73 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
5030b4932b Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 15:22:34 +02:00
e47b34a472 Improves output logging and fixes some bugs
Fixes an error caused by the BuildTools directory not being created
Creates functions for writing to files in CommonFunctions
Adds some more error info to the log when BuildTools fails to download
Fixes some typos
Makes sure all visible text is logged
2021-08-03 15:22:04 +02:00
28291cf4c3 Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 14:00:32 +02:00
f8ba6e7ad1 Fixes the software crashing if an old configuration is used
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 14:00:02 +02:00
c9c2a1b43b Fixes a maven warning
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 13:44:39 +02:00
0425617f4c Updates software version to 1.3
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 13:18:57 +02:00
12189c12d7 Comments out SpongeVanilla and SpongeForge for now
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 12:48:23 +02:00
00cfe78491 Increases readability a bit 2021-08-03 12:28:21 +02:00
dc362eb2c8 Simplifies the version comparator a bit
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 12:23:59 +02:00
73faa4408e Makes the version comparator more generic 2021-08-03 12:14:59 +02:00
85febdf41b Improves version checking to prevent and update notice when running an unreleased version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 11:50:32 +02:00
6ca49d2ccd Adds a menu item for easily delete built .jar files
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 11:06:02 +02:00
429b1fcec0 Improves readability of the server launcher menu code 2021-08-03 10:40:12 +02:00
33795a90a9 Prevents jars from being built as part of jar downloading
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-03 10:16:43 +02:00
e4be75b770 Finishes custom java path
Adds missing menu items
Adds menu item functionality
2021-08-02 22:24:10 +02:00
0018816d90 Makes the software build Spigot and CraftBukkit jars if the latest version is chosen 2021-08-02 21:50:12 +02:00
f1eead3807 Adds various fixes to make the two java versions work as expected
Makes it possible to load a controller without generating a GUI, for better testing
Makes sure not to try and parse empty profile lines
Saves controller settings in a more readable and appendable format
Adds code for using the correct java version for the occasion
Adds a new function for writing to files
2021-08-02 21:06:22 +02:00
6ec44f1f92 Improves console and GUI messages
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Adds newlines when writing to console
Add Error: on lines containing an error
Makes it clearer when a jar build is successful
Makes failures show errors rather than throwing exceptions
2021-08-02 17:57:09 +02:00
d61239c417 Removes BuildTools files after tests have finished 2021-08-02 17:54:35 +02:00
32c17c6005 Improves writing speed when not using a GUI
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Uses buffered writers instead of System.out
Uses a GUI for writing JarBuilder messages
Adds some missing comments to the jar builder
2021-08-02 16:49:35 +02:00
52eacb9980 Adds 1.17 to server versions 2021-08-02 16:22:04 +02:00
043f2045e6 Makes some improvements
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Saves the downloaded BuildTools version to the server version container
Adds a check to see whether the BuildTools file exists
Adds a helper function for reading local files
2021-08-02 16:09:22 +02:00
51ceac69da Makes the jar building finally work
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-08-02 11:38:43 +02:00
123a8eddda Adds some still not working code for building spigot and bukkit .jar files
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-06-11 20:41:30 +02:00
8cdb1f143c Adds a new function for deleting files recursively 2021-02-12 16:12:58 +01:00
3254481326 Improves server waiting and killing
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
Makes the client force stop servers if the stop button is clicked more than once
Prevents new servers from starting after server stop is initialized
2021-02-05 18:41:54 +01:00
fb705f4e13 Adds server console truncation after 1000 lines of output
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-02-05 15:53:29 +01:00
15dd8db31b Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-01-29 20:42:56 +01:00
fa6a0df4c7 Removes the annoying delay before starting the first server when enabling server delay 2021-01-29 20:30:07 +01:00
8b0423e231 Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-01-28 03:09:28 +01:00
b1454b1156 Improves username login/logout checking by using a proper regex match
Additionally, this commit fixes a bug where instead of Username, users are shown as Username[IP:PORT]
2021-01-28 03:08:10 +01:00
a4d7b1041c Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2021-01-27 16:12:03 +01:00
a705fc8b3e Updates Minecraft server jar versions to 1.16.5 2021-01-27 16:11:50 +01:00
5fe4384b59 Updates software version
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2020-12-28 16:55:02 +01:00
2f31a6c248 Updates server version to 1.16.4 and fixes downloading of waterfall and travertine 2020-12-28 16:54:36 +01:00
70f8b87eef Updates software version to 1.2.3
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2020-09-22 15:15:05 +02:00
a9332c0a6c Removes Spigot 1.16.1 as it is deemed unnecessary 2020-09-22 15:14:07 +02:00
2b6315a914 Rewrites console output. Closes #4
Makes the servers be responsible for their own consoles
Starts reading from console once a server starts, and stops once the server stops
Makes reading from a buffered reader read character by character rather than line by line to fix reading from streams
Makes sure each server's console is read on different threads to prevent lockup
Prevents possible console bugs caused by only reading servers already enabled on startup
2020-09-22 15:12:07 +02:00
262418ff7f Updates software version to 1.2.2
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2020-09-20 20:39:13 +02:00
31d167a8a4 Closes a buffered reader in saveStateTest properly and removes a .close causing server consoles not to display text 2020-09-20 20:38:21 +02:00
44b9bcc960 Updates current version to 1.2.1
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
2020-09-20 18:18:45 +02:00
9f2dda7c6b Updates Minecraft .jar version to 1.16.3 where applicable 2020-09-20 18:18:27 +02:00
6c0e352649 Fixes a bug caused by the version file not being properly closed 2020-09-20 17:36:10 +02:00
47 changed files with 2397 additions and 820 deletions

1
.gitignore vendored
View File

@ -28,4 +28,5 @@ hs_err_pid*
bin/ bin/
out/ out/
.idea
*.txt *.txt

View File

@ -1,6 +1,14 @@
# Minecraft-Server-Launcher # 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. As this is a personal one-man project, I prefer to stay in charge of the code. I appreciate code fixing mistakes, or
I appreciate code fixing mistakes, or code improvements, but the software's functionality or purpose, should not be changed. code improvements, but the software's functionality or purpose, should not be changed. I might change anything at any
I might change anything at any time, so you might want to keep that in mind before doing big changes yourself. time, so you might want to keep that in mind before doing big changes yourself.

View File

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

View File

@ -126,9 +126,9 @@
<version>2.8.5</version> <version>2.8.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>4.11</version> <version>5.7.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,8 +1,8 @@
package net.knarcraft.minecraftserverlauncher; package net.knarcraft.minecraftserverlauncher;
import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.Controller; 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.ServerConsoles;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI; import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
@ -11,45 +11,44 @@ import net.knarcraft.minecraftserverlauncher.utility.Updater;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween;
//Java 8 required. //Java 8 required.
/** /**
* A software for managing Minecraft servers. * A software for managing Minecraft servers.
* *
* @author Kristian Knarvik <kristian.knarvik@knett.no> * @author Kristian Knarvik <kristian.knarvik@knett.no>
* @version 1.0.0 * @version b1.3.4
* @since 1.0.0 * @since 1.0.0
*/ */
public class Main { public class Main {
private static String applicationWorkDirectory;
private static boolean serversAreRunning = false;
private static final String updateChannel = "beta"; private static final String updateChannel = "beta";
private static final String updateURL = "https://api.knarcraft.net/minecraftserverlauncher"; private static final String updateURL = "https://api.knarcraft.net/minecraftserverlauncher";
private static final ServerLauncherController controller = ServerLauncherController.getInstance();
private static String applicationWorkDirectory;
private static boolean serversAreRunning = false;
private static ServerLauncherGUI gui; private static ServerLauncherGUI gui;
private static final Controller controller = Controller.getInstance();
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
Updater.checkForUpdate(updateURL, updateChannel); String logFile = Main.getApplicationWorkDirectory() + File.separator + "latestrun.log";
try (PrintWriter file = new PrintWriter(Main.getApplicationWorkDirectory() + File.separator + "latestrun.log")) { CommonFunctions.writeFile(logFile, "[Info]: Starting Minecraft Server Launcher v." +
file.print(""); Updater.getCurrentVersion()[1]);
try {
Updater.checkForUpdate(updateURL, updateChannel);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); CommonFunctions.appendFile(logFile, "[Warning]: Unable to complete update procedure: " + e.getMessage());
} }
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
try { try {
new ServerConsoles(); ServerConsoles.instantiate();
controller.loadState(); controller.loadState();
gui = controller.getGUI(); gui = controller.getGUI();
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(Main::updateConsoles, 10, 500, TimeUnit.MILLISECONDS); exec.scheduleAtFixedRate(Main::updateServersRunningState, 10, 500, TimeUnit.MILLISECONDS);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -61,7 +60,7 @@ public class Main {
* *
* @return <p>The controller used by the software</p> * @return <p>The controller used by the software</p>
*/ */
public static Controller getController() { public static ServerLauncherController getController() {
return controller; return controller;
} }
@ -83,98 +82,35 @@ public class Main {
} }
/** /**
* Reads from server processes, and writes the output to consoles. * Updates the software state if the servers' running state has changed
*/ */
private static void updateConsoles() { private static void updateServersRunningState() {
try { boolean runningNew = serversRunning();
for (Collection collection : controller.getCurrentProfile().getCollections()) { if (serversAreRunning && !runningNew) {
Server server = collection.getServer(); //Servers stopped running
if (server.isEnabled() && server.getProcess() != null) { ServerHandler.serversStopped();
try { gui.updateGUIElementsWhenServersStartOrStop(false);
String readText = CommonFunctions.readBufferedReader(server.getReader()); gui.setStatus("Servers are stopped");
if (!readText.equals("")) { } else if (!serversAreRunning && runningNew) {
collection.getServerConsole().output(readText); //Servers started running
updatePlayerList(readText, server); gui.updateGUIElementsWhenServersStartOrStop(true);
}
} catch (IOException e) {
e.printStackTrace();
}
if (!server.getProcess().isAlive()) {
server.stopped();
}
}
}
boolean runningNew = serversRunning();
if (!runningNew && serversAreRunning) {
gui.updateGUIElementsWhenServersStartOrStop(false);
gui.setStatus("Servers are stopped");
} else if (runningNew && !serversAreRunning) {
gui.updateGUIElementsWhenServersStartOrStop(true);
}
serversAreRunning = runningNew;
} catch (Exception e) {
e.printStackTrace();
} }
serversAreRunning = runningNew;
} }
/** /**
* Goes through all servers and looks for any running servers. * Goes through all servers and looks for any running servers
* *
* @return Is at least one server running? * @return <p>Whether at least one server is running</p>
*/ */
private static boolean serversRunning() { private static boolean serversRunning() {
int num = 0; int serversRunning = 0;
for (Collection collection : controller.getCurrentProfile().getCollections()) { for (Collection collection : controller.getCurrentProfile().getCollections()) {
if (collection.getServer().isStarted() || if (collection.getServer().isStarted() ||
(collection.getServer().getProcess() != null && collection.getServer().getProcess().isAlive())) { (collection.getServer().getProcess() != null && collection.getServer().getProcess().isAlive())) {
num++; serversRunning++;
} }
} }
return num > 0; return serversRunning > 0;
}
/**
* Looks for strings implying a player has joined or left, and updates the appropriate lists
*
* @param text <p>The text to search</p>
* @param server <p>The server which sent the text</p>
*/
private static void updatePlayerList(String text, Server server) {
if (!server.getType().isProxy()) {
String joinedPlayer = getPlayer(text, true);
String leftPlayer = getPlayer(text, false);
if (!joinedPlayer.equals("")) {
if (!server.hasPlayer(joinedPlayer)) {
server.addPlayer(joinedPlayer);
}
} else if (!leftPlayer.equals("")) {
if (server.hasPlayer(leftPlayer)) {
server.removePlayer(leftPlayer);
}
}
}
}
/**
* Searches a string for players joining or leaving
*
* @param text <p>The text string to search through</p>
* @param joined <p>Whether to search for a joining player</p>
* @return <p>The name of a player, or an empty string</p>
*/
private static String getPlayer(String text, boolean joined) {
String playerName;
if (joined) {
playerName = stringBetween(text, "[Server thread/INFO]: ", " joined the game");
if (playerName.equals("")) {
playerName = stringBetween(text, "UUID of player ", " is ");
}
} else {
playerName = stringBetween(text, "INFO]: ", " lost connection");
if (playerName.equals("")) {
playerName = stringBetween(text, "[Server thread/INFO]: ", " left the game");
}
}
return playerName;
} }
} }

View File

@ -2,14 +2,16 @@ package net.knarcraft.minecraftserverlauncher.profile;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.server.Server; import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerConsoles; import net.knarcraft.minecraftserverlauncher.userinterface.ServerConsoles;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI; import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import javax.swing.*; import javax.swing.*;
import java.io.*; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* Keeps track of a set of servers and some user settings * Keeps track of a set of servers and some user settings
@ -21,7 +23,7 @@ import java.util.ArrayList;
public class Profile { public class Profile {
private static final ServerLauncherGUI serverLauncherGui = Main.getController().getGUI(); private static final ServerLauncherGUI serverLauncherGui = Main.getController().getGUI();
private final ArrayList<Collection> collections; private final List<Collection> collections;
private final String name; private final String name;
private boolean runInBackground; private boolean runInBackground;
private int delayStartup; private int delayStartup;
@ -105,7 +107,7 @@ public class Profile {
* *
* @return <p>All collections stored by this profile</p> * @return <p>All collections stored by this profile</p>
*/ */
public ArrayList<Collection> getCollections() { public List<Collection> getCollections() {
return this.collections; return this.collections;
} }
@ -139,14 +141,16 @@ public class Profile {
* @param name <p>The name of the collection and its elements</p> * @param name <p>The name of the collection and its elements</p>
*/ */
public void addCollection(String name) throws ConfigurationException { 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; return;
} }
if (getCollection(name) == null && !name.equals("All") && CommonFunctions.nameIsValid(name)) { if (getCollection(name) == null && !name.equals("All") && CommonFunctions.nameIsValid(name)) {
collections.add(new Collection(name)); collections.add(new Collection(name));
} else { } else {
serverLauncherGui.showError("A server name must my unique and not empty or \"All\"." + serverLauncherGui.showError("A server name must be unique and not empty. " +
"It can't contain any of the characters \"!\", \"?\" or \";\"."); "In addition, a server cannot be named: \"All\". " +
"It cannot contain any of the characters \"!\", \"?\" or \";\".");
} }
} }
@ -230,17 +234,19 @@ public class Profile {
*/ */
public static Profile fromString(String profileString) throws ConfigurationException { public static Profile fromString(String profileString) throws ConfigurationException {
Profile profile; Profile profile;
if (profileString.contains("?")) { if (profileString.equals("")) {
return null;
} else if (profileString.contains("?")) {
String[] data = profileString.split("\\?"); String[] data = profileString.split("\\?");
String[] profileData = data[0].split(";", -1); String[] profileData = data[0].split(";", -1);
profile = parseProfile(profileData); profile = parseProfile(profileData);
if (data[1].contains("!")) { if (data[1].contains("!")) {
String[] servers = data[1].split("!", -1); String[] servers = data[1].split("!", -1);
for (String server : servers) { for (String server : servers) {
profile.collections.add(new Collection(Server.fromString(server))); profile.collections.add(new Collection(ServerHandler.fromString(server)));
} }
} else { } else {
profile.collections.add(new Collection(Server.fromString(data[1]))); profile.collections.add(new Collection(ServerHandler.fromString(data[1])));
} }
} else { } else {
profile = parseProfile(profileString.split(";", -1)); 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.Main;
import net.knarcraft.minecraftserverlauncher.server.Server; import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler; import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI; import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerTab; import net.knarcraft.minecraftserverlauncher.userinterface.ServerTab;
@ -9,16 +10,20 @@ import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import net.knarcraft.minecraftserverlauncher.utility.JarDownloader; import net.knarcraft.minecraftserverlauncher.utility.JarDownloader;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import java.io.*; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
/** /**
* This class handles profiles, GUI creation and session restoration * This class handles profiles, GUI creation and session restoration
*/ */
public class Controller { public class ServerLauncherController {
private final String workingDirectory = Main.getApplicationWorkDirectory() + File.separator + "files"; private final String workingDirectory = Main.getApplicationWorkDirectory() + File.separator + "files";
private final String profilesFile = workingDirectory + File.separator + "Profiles.txt"; private final String profilesFile = workingDirectory + File.separator + "Profiles.txt";
@ -28,12 +33,14 @@ public class Controller {
private ServerLauncherGUI serverLauncherGUI; private ServerLauncherGUI serverLauncherGUI;
private Profile currentProfile; private Profile currentProfile;
private boolean downloadAllJars; private boolean downloadAllJars;
private static Controller controller; private static ServerLauncherController controller;
private String javaCommand = "java";
private String oldJavaCommand = "java";
/** /**
* Instantiates a new controller * Instantiates a new controller
*/ */
private Controller() { private ServerLauncherController() {
this.profileList = new ArrayList<>(); this.profileList = new ArrayList<>();
} }
@ -42,9 +49,9 @@ public class Controller {
* *
* @return <p>An instance of the controller</p> * @return <p>An instance of the controller</p>
*/ */
public static Controller getInstance() { public static ServerLauncherController getInstance() {
if (controller == null) { if (controller == null) {
controller = new Controller(); controller = new ServerLauncherController();
} }
return controller; return controller;
} }
@ -85,6 +92,47 @@ public class Controller {
return currentProfile.getDelayStartup(); return currentProfile.getDelayStartup();
} }
/**
* Gets the command for running java
*
* @return <p>The command for running Java</p>
*/
public String getJavaCommand() {
return javaCommand;
}
/**
* Gets the command for running older versions of Java
*
* <p>The command used to run older minecraft server versions. JRE 8 should probably work.
* Can be just "java" or a file path.</p>
*
* @return <p>The command for running older versions of Java</p>
*/
public String getOldJavaCommand() {
return oldJavaCommand;
}
/**
* Sets the command for running Java
*
* <p>To play on the newest version of Minecraft, this needs to be JDK 16. Can be just "java" or a file path.</p>
*
* @param javaCommand <p>The command used for running Java</p>
*/
public void setJavaCommand(String javaCommand) {
this.javaCommand = javaCommand;
}
/**
* Sets the command for running older versions of java
*
* @param oldJavaCommand <p>The command used for running older versions of Java</p>
*/
public void setOldJavaCommand(String oldJavaCommand) {
this.oldJavaCommand = oldJavaCommand;
}
@Override @Override
public String toString() { public String toString() {
int guiWidth; int guiWidth;
@ -96,7 +144,8 @@ public class Controller {
guiWidth = serverLauncherGUI.getSize().width; guiWidth = serverLauncherGUI.getSize().width;
guiHeight = serverLauncherGUI.getSize().height; guiHeight = serverLauncherGUI.getSize().height;
} }
return String.format("%s;%d;%d;%b", currentProfile.getName(), guiWidth, guiHeight, downloadAllJars); return String.format("selectedProfile;%s\nguiWidth;%d\nguiHeight;%d\ndownloadAllJars;%b\njavaCommand;%s\noldJavaCommand;%s",
currentProfile.getName(), guiWidth, guiHeight, downloadAllJars, javaCommand, oldJavaCommand);
} }
/** /**
@ -170,84 +219,6 @@ public class Controller {
return this.currentProfile; return this.currentProfile;
} }
/**
* Loads data about the controller from a save string
*
* @param data <p>The save string to load</p>
* @return <p>The currently selected profile</p>
* @throws IOException <p>If unable to create the GUI</p>
*/
private String fromString(String data) throws IOException {
String[] dataList = data.split(";");
this.downloadAllJars = Boolean.parseBoolean(dataList[3]);
this.serverLauncherGUI = new ServerLauncherGUI(Integer.parseInt(dataList[1]), Integer.parseInt(dataList[2]));
return dataList[0];
}
/**
* Reads profiles and servers from a text file
*/
public void loadState() throws ConfigurationException, IOException {
//Loads data regarding this controller
String currentProfile = null;
if (new File(mainFile).exists()) {
String controllerData = CommonFunctions.readBufferedReader(new BufferedReader(new InputStreamReader(new FileInputStream(mainFile))));
currentProfile = this.fromString(controllerData);
} else {
this.serverLauncherGUI = new ServerLauncherGUI();
}
//Loads all saved profiles
loadProfiles(currentProfile);
//Makes the GUI show the loaded data
executeGUILoadingTasks();
}
/**
* Loads all saved profiles
*
* @param currentProfile <p>The profile saved as the current profile</p>
* @throws ConfigurationException <p>If unable to load a profile</p>
*/
private void loadProfiles(String currentProfile) throws ConfigurationException {
try (Scanner in = new Scanner(new File(profilesFile))) {
while (in.hasNextLine()) {
String profileData = in.nextLine();
Profile loadedProfile = Profile.fromString(profileData);
profileList.add(loadedProfile);
}
} catch (FileNotFoundException e) {
addProfile("Default");
}
this.currentProfile = getProfileByName(currentProfile);
if (this.currentProfile == null) {
this.currentProfile = this.profileList.get(0);
}
}
/**
* Updates the GUI as necessary to display loaded data
*/
private void executeGUILoadingTasks() {
this.serverLauncherGUI.updateProfiles();
this.serverLauncherGUI.updateWithSavedProfileData();
this.currentProfile.updateConsoles();
if (this.downloadAllJars) {
Executors.newSingleThreadExecutor().execute(() -> {
JarDownloader downloader = new JarDownloader(serverLauncherGUI, jarDirectory);
try {
downloader.downloadJars();
} catch (IOException e) {
e.printStackTrace();
}
});
}
if (this.currentProfile.getRunInBackground()) {
Executors.newSingleThreadExecutor().execute(Server::startServers);
this.serverLauncherGUI.hideToTray();
}
}
/** /**
* Sets the download all jars option * Sets the download all jars option
* *
@ -266,6 +237,15 @@ public class Controller {
this.currentProfile = getProfileByName(profileName); this.currentProfile = getProfileByName(profileName);
} }
/**
* Gets the directory containing .jar files
*
* @return <p>The directory containing .jar files</p>
*/
public String getJarDirectory() {
return this.jarDirectory;
}
/** /**
* Saves the state of the entire application to disk * Saves the state of the entire application to disk
*/ */
@ -273,20 +253,151 @@ public class Controller {
saveGUIStateToServer(); saveGUIStateToServer();
try { try {
CommonFunctions.createAllFolders(); CommonFunctions.createAllFolders();
PrintWriter mFile = new PrintWriter(mainFile); CommonFunctions.writeFile(mainFile, this.toString());
mFile.println(this.toString()); StringBuilder builder = new StringBuilder();
mFile.close();
PrintWriter pFile = new PrintWriter(profilesFile);
for (Profile profile : profileList) { for (Profile profile : profileList) {
pFile.println(profile.toString()); builder.append(profile.toString()).append("\n");
} }
pFile.close(); CommonFunctions.writeFile(profilesFile, builder.toString());
} catch (FileNotFoundException e) { } catch (IOException e) {
serverLauncherGUI.showError("Unable to save data."); serverLauncherGUI.showError("Unable to save data.");
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
* Reads profiles and servers from a text file
*/
public void loadState() throws ConfigurationException, IOException {
loadState(false);
}
/**
* Reads profiles and servers from a text file
*
* @param silent <p>If silent, no GUI will be created</p>
*/
public void loadState(boolean silent) throws ConfigurationException, IOException {
//Loads data regarding this controller
String currentProfile = null;
if (new File(mainFile).exists()) {
currentProfile = this.fromString(silent);
} else if (!silent) {
this.serverLauncherGUI = new ServerLauncherGUI();
}
if (silent) {
return;
}
//Loads all saved profiles
loadProfiles(currentProfile);
//Makes the GUI show the loaded data
executeGUILoadingTasks();
}
/**
* Loads data about the controller from a save string
*
* @param silent <p>If silent, no GUI will be created</p>
* @return <p>The currently selected profile</p>
*/
private String fromString(boolean silent) {
Map<String, String> loadedData = new HashMap<>();
try {
String currentData = CommonFunctions.readFile(mainFile);
for (String line : currentData.split("\n")) {
parseSaveLine(loadedData, line);
}
this.downloadAllJars = Boolean.parseBoolean(loadedData.get("downloadAllJars"));
if (!silent) {
if (loadedData.get("guiWidth") != null && loadedData.get("guiHeight") != null) {
this.serverLauncherGUI = new ServerLauncherGUI(Integer.parseInt(loadedData.get("guiWidth")),
Integer.parseInt(loadedData.get("guiHeight")));
} else {
this.serverLauncherGUI = new ServerLauncherGUI();
}
} else {
this.serverLauncherGUI = new ServerLauncherGUI(true);
}
if (loadedData.get("javaCommand") != null) {
this.javaCommand = loadedData.get("javaCommand");
}
if (loadedData.get("oldJavaCommand") != null) {
this.oldJavaCommand = loadedData.get("oldJavaCommand");
}
return loadedData.get("selectedProfile");
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Saves one config line to the provided map
*
* @param loadedData <p>The map to save the loaded configuration to</p>
* @param line <p>The line to read</p>
*/
private void parseSaveLine(Map<String, String> loadedData, String line) {
String[] lineData = line.split(";");
if (lineData.length != 2) {
return;
}
loadedData.put(lineData[0], lineData[1]);
}
/**
* Loads all saved profiles
*
* @param currentProfile <p>The profile saved as the current profile</p>
* @throws ConfigurationException <p>If unable to load a profile</p>
*/
private void loadProfiles(String currentProfile) throws ConfigurationException {
try (Scanner in = new Scanner(new File(profilesFile))) {
while (in.hasNextLine()) {
String profileData = in.nextLine();
Profile loadedProfile = Profile.fromString(profileData);
if (loadedProfile == null) {
continue;
}
profileList.add(loadedProfile);
}
} catch (FileNotFoundException e) {
addProfile("Default");
}
this.currentProfile = getProfileByName(currentProfile);
if (this.currentProfile == null) {
this.currentProfile = this.profileList.get(0);
}
}
/**
* Updates the GUI as necessary to display loaded data
*/
private void executeGUILoadingTasks() {
this.serverLauncherGUI.getControlPanelTab().updateProfiles();
this.serverLauncherGUI.updateWithSavedProfileData();
this.currentProfile.updateConsoles();
if (this.downloadAllJars) {
Executors.newSingleThreadExecutor().execute(() -> {
JarDownloader downloader = new JarDownloader(serverLauncherGUI, jarDirectory);
try {
downloader.downloadJars();
} catch (IOException e) {
e.printStackTrace();
}
});
}
if (this.currentProfile.getRunInBackground()) {
Executors.newSingleThreadExecutor().execute(ServerHandler::startServers);
this.serverLauncherGUI.hideToTray();
}
}
/** /**
* Updates the server object with the current state of the GUI server tab * Updates the server object with the current state of the GUI server tab
*/ */

View File

@ -0,0 +1,179 @@
package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
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;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.file.Paths;
/**
* The jar builder is responsible for building the newest Spigot and CraftBukkit .jar files using BuildTools
*/
public class JarBuilder {
private final String buildDirectory;
private final String jarDirectory;
private final ServerVersionContainer versionContainer;
private final GUI gui;
private final String javaCommand;
/**
* Instantiates a new jar builder
*
* @param buildDirectory <p>The directory containing BuildTool files</p>
* @param jarDirectory <p>The directory containing downloaded .jar files</p>
* @param gui <p>The GUI to write messages to</p>
*/
public JarBuilder(String buildDirectory, String jarDirectory, GUI gui) {
this.buildDirectory = buildDirectory;
this.jarDirectory = jarDirectory;
this.gui = gui;
this.versionContainer = ServerVersionContainer.getInstance();
this.javaCommand = ServerLauncherController.getInstance().getJavaCommand();
try {
CommonFunctions.createFolder(new File(buildDirectory));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* Builds the latest version of the spigot .jar file
*/
public boolean buildSpigotJar() {
gui.setStatus("Building Spigot jar ...");
downloadBuildTools();
ProcessBuilder processBuilder = new ProcessBuilder(javaCommand, "-jar", "BuildTools.jar", "--rev",
"latest", "--output-dir", jarDirectory);
if (executeBuildProcess(processBuilder) && moveBuiltJar("spigot-", "SpigotLatest.jar")) {
gui.setStatus("Finished moving spigot.jar");
return true;
} else {
return false;
}
}
/**
* Builds the latest version of the craftbukkit .jar file
*/
public boolean buildBukkitJar() {
gui.setStatus("Building Bukkit jar ...");
downloadBuildTools();
ProcessBuilder processBuilder = new ProcessBuilder(javaCommand, "-jar", "BuildTools.jar", "--compile",
"craftbukkit", "--rev", "latest", "--output-dir", jarDirectory);
if (executeBuildProcess(processBuilder) && moveBuiltJar("craftbukkit-", "BukkitLatest.jar")) {
gui.setStatus("Finished moving craftbukkit.jar");
return true;
} else {
return false;
}
}
/**
* Downloads the latest BuildTools version
*/
public void downloadBuildTools() {
ServerLauncherGUI gui = Main.getController().getGUI();
try {
String latestVersion = getLatestBuildToolsVersion();
boolean exists = new File(buildDirectory + "BuildTools.jar").exists();
boolean isUpdated = latestVersion.equals(versionContainer.getDownloadedBuildToolsVersion());
if (!exists || !isUpdated) {
String buildToolsURL = "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar";
boolean success = CommonFunctions.downloadFile(buildToolsURL, Paths.get(buildDirectory + "BuildTools.jar"));
if (!success) {
gui.setStatus("Unable to download the latest BuildTools");
gui.logError("Target: " + buildDirectory + "BuildTools.jar");
gui.logError("URL: " + buildToolsURL);
} else {
versionContainer.setDownloadedBuildToolsVersion(latestVersion);
}
}
} catch (IOException e) {
gui.setStatus("Unable to download the latest BuildTools");
gui.logMessage(e.getMessage());
}
}
/**
* Moves a built .jar file to its target file
*
* @param searchString <p>The start of the name of the built file used to find it</p>
* @param newName <p>The new name of the built file</p>
* @return <p>True if the .jar file was moved successfully</p>
*/
private boolean moveBuiltJar(String searchString, String newName) {
File[] foundFiles = new File(jarDirectory).listFiles((file) -> file.isFile() && file.getName().startsWith(searchString));
if (foundFiles != null && foundFiles.length == 1) {
File newFileLocation = new File(jarDirectory + File.separator + newName);
if (newFileLocation.exists()) {
if (!newFileLocation.delete()) {
gui.showError("Unable to delete previously built .jar");
}
}
if (!foundFiles[0].renameTo(newFileLocation)) {
gui.showError("Unable to move built .jar");
} else {
return true;
}
}
return false;
}
/**
* Starts the build process and initializes
*
* @param processBuilder <p>The process builder to execute</p>
* @return <p>True if the process exited successfully</p>
*/
private boolean executeBuildProcess(ProcessBuilder processBuilder) {
processBuilder.directory(new File(buildDirectory));
processBuilder.redirectErrorStream(true);
try {
Process process = processBuilder.start();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
do {
writer.write(CommonFunctions.readBufferedReader(reader));
} while (process.isAlive());
writer.newLine();
writer.flush();
if (process.exitValue() == 0) {
gui.setStatus("Jar building process finished successfully.");
return true;
} else {
gui.showError("Jar building process failed with exit code " + process.exitValue() +
". Please check the BuildTools log for errors.");
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* Gets the latest build tools version available
*
* @return <p>The latest build tools version available</p>
* @throws IOException <p>If unable to read the version file</p>
*/
String getLatestBuildToolsVersion() throws IOException {
String versionDocument;
versionDocument = CommonFunctions.readRemoteFile("https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/");
return CommonFunctions.stringBetween(versionDocument, "BuildTools #", " [");
}
}

View File

@ -1,9 +1,10 @@
package net.knarcraft.minecraftserverlauncher.server; package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.profile.Controller;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType; import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
import net.knarcraft.minecraftserverlauncher.userinterface.ServerLauncherGUI;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -15,7 +16,11 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
@ -26,14 +31,9 @@ import java.util.concurrent.TimeUnit;
* @since 1.0.0 * @since 1.0.0
*/ */
public class Server { 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" + private static final String jarDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" +
File.separator + "Jars" + File.separator; File.separator + "Jars" + File.separator;
private final String name; private final String name;
private final ArrayList<String> playerList; private final ArrayList<String> playerList;
private String path; private String path;
@ -45,6 +45,8 @@ public class Server {
private BufferedWriter writer; private BufferedWriter writer;
private BufferedReader reader; private BufferedReader reader;
private boolean started; private boolean started;
private ScheduledExecutorService consoleOutputExecutor;
private final ServerLauncherGUI gui = Main.getController().getGUI();
/** /**
* Initializes a new server with default values * Initializes a new server with default values
@ -58,7 +60,7 @@ public class Server {
this.playerList = new ArrayList<>(); this.playerList = new ArrayList<>();
this.type = null; this.type = null;
this.serverVersion = ""; this.serverVersion = "";
this.maxRam = ramList[0]; this.maxRam = ServerHandler.getRamList()[0];
this.process = null; this.process = null;
this.writer = null; this.writer = null;
this.reader = null; this.reader = null;
@ -84,76 +86,15 @@ public class Server {
this.playerList = new ArrayList<>(); 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 * Gets the buffered reader used to read from this server
* *
* @return <p>The buffered reader used to read from this server</p> * @return <p>The buffered reader used to read from this server</p>
*/ */
public BufferedReader getReader() { private BufferedReader getReader() {
return this.reader; return this.reader;
} }
/**
* 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 {
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;
}
}
}
/**
* Runs all enabled servers with their settings
*/
public static void startServers() {
Controller controller = Main.getController();
controller.getGUI().setStatus("Starting servers");
for (Collection collection : controller.getCurrentProfile().getCollections()) {
if (!collection.getServer().runServer()) {
controller.getGUI().setStatus("An error occurred. Start aborted");
try {
Server.stop();
} catch (IOException 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 * Gets the name of the server
* *
@ -219,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 * Gets the path for this server's files
* *
@ -274,9 +251,10 @@ public class Server {
} }
/** /**
* Checks whether this server is fully stopped * Removes all information about the server's process, writer and reader
*/ */
public void stopped() { private void cleanStoppedServerValues() {
consoleOutputExecutor.shutdown();
process = null; process = null;
writer = null; writer = null;
reader = null; reader = null;
@ -307,7 +285,7 @@ public class Server {
* @param name <p>The name of the player to check</p> * @param name <p>The name of the player to check</p>
* @return <p>True if the player is connected</p> * @return <p>True if the player is connected</p>
*/ */
public boolean hasPlayer(String name) { private boolean hasPlayer(String name) {
for (String player : this.playerList) { for (String player : this.playerList) {
if (player.equals(name)) { if (player.equals(name)) {
return true; return true;
@ -321,9 +299,9 @@ public class Server {
* *
* @param name <p>The name of the player to add</p> * @param name <p>The name of the player to add</p>
*/ */
public void addPlayer(String name) { private void addPlayer(String name) {
this.playerList.add(name); this.playerList.add(name);
Main.getController().getGUI().getServerControlTab().addPlayer(name); gui.getServerControlTab().addPlayer(name);
} }
/** /**
@ -331,9 +309,9 @@ public class Server {
* *
* @param name <p>The name of the player to remove</p> * @param name <p>The name of the player to remove</p>
*/ */
public void removePlayer(String name) { private void removePlayer(String name) {
playerList.removeIf(player -> player.equals(name)); playerList.removeIf(player -> player.equals(name));
Main.getController().getGUI().getServerControlTab().removePlayer(name); gui.getServerControlTab().removePlayer(name);
} }
/** /**
@ -348,32 +326,63 @@ public class Server {
/** /**
* Runs a Minecraft 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> * @return <p>True if nothing went wrong</p>
*/ */
private boolean runServer() { public boolean runServer(boolean skipDelay) {
if (ServerHandler.stoppingServers()) {
gui.logMessage("Stopping servers. Cannot start yet.");
return false;
}
//Ignore a disabled server //Ignore a disabled server
if (!this.enabled) { if (!this.enabled) {
this.started = false; this.started = false;
return true; return true;
} }
//Tries to do necessary pre-start work //Tries to do necessary pre-start work
if (!initializeJarDownload() || !delayStartup()) { if (!initializeJarDownload() || (!skipDelay && !delayStartup())) {
gui.logError("Failed to perform startup tasks.");
this.started = false; this.started = false;
return false; return false;
} }
if (ServerHandler.stoppingServers()) {
gui.logMessage("Stopping servers. Cannot start yet.");
return false;
}
//Starts the server if possible //Starts the server if possible
try { try {
startServerProcess(); startServerProcess();
Main.getController().getGUI().setStatus("Servers are running"); gui.setStatus("Servers are running");
this.started = true; this.started = true;
return true; return true;
} catch (IOException e) { } catch (IOException e) {
Main.getController().getGUI().setStatus("Could not start server"); gui.setStatus("Could not start server");
this.started = false; this.started = false;
return false; return false;
} }
} }
/**
* Gets the correct java command to use for the selected server version
*
* @return <p>The java version to run</p>
*/
private String getJavaCommand() {
ServerLauncherController controller = ServerLauncherController.getInstance();
if (serverVersion.toLowerCase().contains("latest")) {
return controller.getJavaCommand();
} else if (serverVersion.contains(".") && serverVersion.split("\\.").length >= 2) {
try {
if (Integer.parseInt(serverVersion.split("\\.")[1]) >= 17) {
return controller.getJavaCommand();
}
} catch (NumberFormatException ignored) {
}
}
return controller.getOldJavaCommand();
}
/** /**
* Starts the process running this server * Starts the process running this server
* *
@ -388,7 +397,7 @@ public class Server {
} else { } else {
serverPath = jarDirectory + this.type.getName() + serverVersion + ".jar"; serverPath = jarDirectory + this.type.getName() + serverVersion + ".jar";
} }
builder = new ProcessBuilder("java", "-Xmx" + this.maxRam, "-Xms512M", builder = new ProcessBuilder(getJavaCommand(), "-Xmx" + this.maxRam, "-Xms512M",
"-Djline.terminal=jline.UnsupportedTerminal", "-Dcom.mojang.eula.agree=true", "-jar", serverPath, "-Djline.terminal=jline.UnsupportedTerminal", "-Dcom.mojang.eula.agree=true", "-jar", serverPath,
"nogui"); "nogui");
builder.directory(new File(this.path)); builder.directory(new File(this.path));
@ -396,6 +405,100 @@ public class Server {
this.process = builder.start(); this.process = builder.start();
this.writer = new BufferedWriter(new OutputStreamWriter(this.process.getOutputStream())); this.writer = new BufferedWriter(new OutputStreamWriter(this.process.getOutputStream()));
this.reader = new BufferedReader(new InputStreamReader(this.process.getInputStream())); this.reader = new BufferedReader(new InputStreamReader(this.process.getInputStream()));
//Start the process for reading from the server's console
consoleOutputExecutor = Executors.newSingleThreadScheduledExecutor();
consoleOutputExecutor.scheduleAtFixedRate(() -> {
try {
updateConsole();
} catch (IOException e) {
e.printStackTrace();
}
}, 10, 500, TimeUnit.MILLISECONDS);
}
/**
* Updates a single console with process output
*
* @throws IOException <p>If unable to read from the server's buffered reader</p>
*/
private void updateConsole() throws IOException {
String readText = CommonFunctions.readBufferedReader(getReader());
if (!readText.equals("")) {
Main.getController().getCurrentProfile().getCollection(getName()).getServerConsole().output(readText);
updatePlayerList(readText);
}
if (!getProcess().isAlive()) {
cleanStoppedServerValues();
}
}
/**
* Looks for strings implying a player has joined or left, and updates the appropriate lists
*
* @param text <p>The text to search</p>
*/
private void updatePlayerList(String text) {
if (!getType().isProxy()) {
String joinedPlayer = getPlayer(text, true);
String leftPlayer = getPlayer(text, false);
if (!joinedPlayer.equals("")) {
if (!hasPlayer(joinedPlayer)) {
addPlayer(joinedPlayer);
}
} else if (!leftPlayer.equals("")) {
if (hasPlayer(leftPlayer)) {
removePlayer(leftPlayer);
}
}
}
}
/**
* Searches a string for players joining or leaving
*
* @param text <p>The text string to search through</p>
* @param joined <p>Whether to search for a joining player</p>
* @return <p>The name of a player, or an empty string</p>
*/
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 logoutPattern1 = "INFO]: ([A-Z0-9a-z_]+) lost connection";
String logoutPattern2 = " ([A-Z0-9a-z_]+) left the game";
if (joined) {
playerName = getFirstRegexCaptureGroup(loginPattern1, text);
if (playerName.equals("")) {
playerName = getFirstRegexCaptureGroup(loginPattern2, text);
}
} else {
playerName = getFirstRegexCaptureGroup(logoutPattern1, text);
if (playerName.equals("")) {
playerName = getFirstRegexCaptureGroup(logoutPattern2, text);
}
}
return playerName;
}
/**
* Returns the first regex capture group found in a pattern
*
* @param pattern <p>The regex pattern to use</p>
* @param text <p>The string to execute the pattern on</p>
* @return <p>The first capture group if a match is found. An empty string otherwise</p>
*/
private String getFirstRegexCaptureGroup(String pattern, String text) {
Pattern compiledPattern = Pattern.compile(pattern);
Matcher patternMatcher = compiledPattern.matcher(text);
if (patternMatcher.find()) {
return patternMatcher.group(1);
} else {
return "";
}
} }
/** /**
@ -405,7 +508,7 @@ public class Server {
*/ */
private boolean delayStartup() { private boolean delayStartup() {
try { try {
Main.getController().getGUI().setStatus("Delaying startup"); gui.setStatus("Delaying startup");
TimeUnit.SECONDS.sleep(Main.getController().getCurrentProfile().getDelayStartup()); TimeUnit.SECONDS.sleep(Main.getController().getCurrentProfile().getDelayStartup());
return true; return true;
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -421,18 +524,15 @@ public class Server {
* @return <p>True if nothing went wrong</p> * @return <p>True if nothing went wrong</p>
*/ */
private boolean initializeJarDownload() { private boolean initializeJarDownload() {
Controller controller = Main.getController(); try {
if (!controller.getDownloadAllJars()) { gui.setStatus("Downloading jar...");
try { this.downloadJar();
controller.getGUI().setStatus("Downloading jar..."); gui.setStatus("File downloaded");
this.downloadJar(); } catch (IOException e) {
controller.getGUI().setStatus("File downloaded"); gui.setStatus("Error: Jar file could not be found, downloaded or built.");
} catch (IOException e) { gui.logError("Unable to get required .jar file: " + e.getMessage());
controller.getGUI().setStatus("Error: Jar file not found"); this.started = false;
e.printStackTrace(); return false;
this.started = false;
return false;
}
} }
return true; 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

@ -1,6 +1,17 @@
package net.knarcraft.minecraftserverlauncher.server; package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.server.servertypes.*; import net.knarcraft.minecraftserverlauncher.server.servertypes.BungeeCord;
import net.knarcraft.minecraftserverlauncher.server.servertypes.CraftBukkit;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Custom;
import net.knarcraft.minecraftserverlauncher.server.servertypes.MCPCPlus;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Paper;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Spigot;
import net.knarcraft.minecraftserverlauncher.server.servertypes.SpongeForge;
import net.knarcraft.minecraftserverlauncher.server.servertypes.SpongeVanilla;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Travertine;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Vanilla;
import net.knarcraft.minecraftserverlauncher.server.servertypes.Waterfall;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -76,8 +87,13 @@ public class ServerTypeHandler {
throw new ConfigurationException("Server type configuration file is missing."); throw new ConfigurationException("Server type configuration file is missing.");
} }
while (file.hasNextLine()) { while (file.hasNextLine()) {
String nextLine = file.nextLine();
if (nextLine.startsWith("#")) {
continue;
}
//Splits the next file line into arguments //Splits the next file line into arguments
String[] serverTypeInfo = file.nextLine().split(";", -1); String[] serverTypeInfo = nextLine.split(";", -1);
//Gets list of server versions from file line //Gets list of server versions from file line
String[] serverVersions; String[] serverVersions;
@ -118,7 +134,7 @@ public class ServerTypeHandler {
break; break;
case "Paper": case "Paper":
newType = new Paper("Paper", false, serverVersions, serverTypeInfo[2], newType = new Paper("Paper", false, serverVersions, serverTypeInfo[2],
serverTypeInfo[3]); serverTypeInfo[3], serverTypeInfo[4], serverTypeInfo[5]);
break; break;
case "Bungee": case "Bungee":
newType = new BungeeCord("Bungee", true, serverVersions, serverTypeInfo[2], newType = new BungeeCord("Bungee", true, serverVersions, serverTypeInfo[2],

View File

@ -3,7 +3,10 @@ package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import java.io.*; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -19,8 +22,10 @@ public class ServerVersionContainer {
private String bungeeVersion; private String bungeeVersion;
private Map<String, String> waterfallVersions; private Map<String, String> waterfallVersions;
private Map<String, String> travertineVersions; private Map<String, String> travertineVersions;
private Map<String, String> paperVersions;
private Map<String, String> spongeVanillaVersions; private Map<String, String> spongeVanillaVersions;
private Map<String, String> spongeForgeVersions; private Map<String, String> spongeForgeVersions;
private String downloadedBuildToolsVersion;
/** /**
* Initializes a new server version container * Initializes a new server version container
@ -28,6 +33,7 @@ public class ServerVersionContainer {
private ServerVersionContainer() { private ServerVersionContainer() {
this.waterfallVersions = new HashMap<>(); this.waterfallVersions = new HashMap<>();
this.travertineVersions = new HashMap<>(); this.travertineVersions = new HashMap<>();
this.paperVersions = new HashMap<>();
this.spongeVanillaVersions = new HashMap<>(); this.spongeVanillaVersions = new HashMap<>();
this.spongeForgeVersions = new HashMap<>(); this.spongeForgeVersions = new HashMap<>();
loadState(); loadState();
@ -48,14 +54,16 @@ public class ServerVersionContainer {
/** /**
* Resets the state of the server version container * Resets the state of the server version container
*/ */
public void reset() { void reset() {
this.vanillaVersion = null; this.vanillaVersion = null;
this.snapshotVersion = null; this.snapshotVersion = null;
this.bungeeVersion = null; this.bungeeVersion = null;
this.waterfallVersions = new HashMap<>(); this.waterfallVersions = new HashMap<>();
this.travertineVersions = new HashMap<>(); this.travertineVersions = new HashMap<>();
this.paperVersions = new HashMap<>();
this.spongeVanillaVersions = new HashMap<>(); this.spongeVanillaVersions = new HashMap<>();
this.spongeForgeVersions = new HashMap<>(); this.spongeForgeVersions = new HashMap<>();
this.downloadedBuildToolsVersion = null;
} }
@Override @Override
@ -65,8 +73,10 @@ public class ServerVersionContainer {
"bungeeVersion;" + bungeeVersion + "\n" + "bungeeVersion;" + bungeeVersion + "\n" +
"waterfallVersions;" + mapToString(waterfallVersions) + "\n" + "waterfallVersions;" + mapToString(waterfallVersions) + "\n" +
"travertineVersions;" + mapToString(travertineVersions) + "\n" + "travertineVersions;" + mapToString(travertineVersions) + "\n" +
"paperVersions;" + mapToString(paperVersions) + "\n" +
"spongeVanillaVersions;" + mapToString(spongeVanillaVersions) + "\n" + "spongeVanillaVersions;" + mapToString(spongeVanillaVersions) + "\n" +
"spongeForgeVersions;" + mapToString(spongeForgeVersions); "spongeForgeVersions;" + mapToString(spongeForgeVersions) + "\n" +
"downloadedBuildToolsVersion;" + downloadedBuildToolsVersion;
} }
/** /**
@ -92,7 +102,7 @@ public class ServerVersionContainer {
/** /**
* Tries to save the state of this server version container * Tries to save the state of this server version container
*/ */
public void saveState() { void saveState() {
File saveFile = new File(versionFile); File saveFile = new File(versionFile);
PrintWriter file; PrintWriter file;
try { try {
@ -105,7 +115,7 @@ public class ServerVersionContainer {
} }
} }
file = new PrintWriter(versionFile); file = new PrintWriter(versionFile);
file.println(this.toString()); file.println(this);
file.close(); file.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -116,13 +126,11 @@ public class ServerVersionContainer {
* Loads the object state from the save file * Loads the object state from the save file
*/ */
private void loadState() { private void loadState() {
BufferedReader reader;
if (!new File(versionFile).exists()) { if (!new File(versionFile).exists()) {
return; return;
} }
try { try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(versionFile))); String currentData = CommonFunctions.readFile(versionFile);
String currentData = CommonFunctions.readBufferedReader(reader);
for (String line : currentData.split("\n")) { for (String line : currentData.split("\n")) {
parseSaveLine(line); parseSaveLine(line);
} }
@ -159,12 +167,18 @@ public class ServerVersionContainer {
case "travertineVersions": case "travertineVersions":
parseVersionsToMap(travertineVersions, variableValue); parseVersionsToMap(travertineVersions, variableValue);
break; break;
case "paperVersions":
parseVersionsToMap(paperVersions, variableValue);
break;
case "spongeVanillaVersions": case "spongeVanillaVersions":
parseVersionsToMap(spongeVanillaVersions, variableValue); parseVersionsToMap(spongeVanillaVersions, variableValue);
break; break;
case "spongeForgeVersions": case "spongeForgeVersions":
parseVersionsToMap(spongeForgeVersions, variableValue); parseVersionsToMap(spongeForgeVersions, variableValue);
break; break;
case "downloadedBuildToolsVersion":
downloadedBuildToolsVersion = variableValue;
break;
default: default:
throw new IllegalArgumentException("Invalid key encountered in the server version file."); throw new IllegalArgumentException("Invalid key encountered in the server version file.");
} }
@ -174,9 +188,9 @@ public class ServerVersionContainer {
* Reads versions from a text string and updates the version map * Reads versions from a text string and updates the version map
* *
* @param targetMap <p>The map to update</p> * @param targetMap <p>The map to update</p>
* @param data <p>The data string to parse</p> * @param data <p>The data string to parse</p>
*/ */
private void parseVersionsToMap(Map<String,String> targetMap, String data) { private void parseVersionsToMap(Map<String, String> targetMap, String data) {
String[] versions = data.split(","); String[] versions = data.split(",");
for (String version : versions) { for (String version : versions) {
String[] versionData = version.split("!"); String[] versionData = version.split("!");
@ -284,6 +298,27 @@ public class ServerVersionContainer {
saveState(); 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 * Gets a specific sponge vanilla version
* *
@ -325,5 +360,25 @@ public class ServerVersionContainer {
spongeForgeVersions.put(mapKey, newValue); spongeForgeVersions.put(mapKey, newValue);
saveState(); saveState();
} }
/**
* Gets the version of the downloaded BuildTools file
*
* @return <p>The version of the downloaded BuildTools file</p>
*/
public String getDownloadedBuildToolsVersion() {
return this.downloadedBuildToolsVersion;
}
/**
* Sets the version of the downloaded BuildTools file
*
* @param newValue <p>The new version</p></p>
*/
public void setDownloadedBuildToolsVersion(String newValue) {
this.downloadedBuildToolsVersion = newValue;
saveState();
}
} }

View File

@ -7,7 +7,7 @@ import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readRemoteFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween;
/** /**
@ -65,7 +65,7 @@ public class BungeeCord extends AbstractServerType {
private boolean downloadLatestJar(File filePath) throws IOException { private boolean downloadLatestJar(File filePath) throws IOException {
ServerVersionContainer versionContainer = ServerVersionContainer.getInstance(); ServerVersionContainer versionContainer = ServerVersionContainer.getInstance();
String newestVersion = stringBetween(readFile(versionURL), srcStart, srcEnd); String newestVersion = stringBetween(readRemoteFile(versionURL), srcStart, srcEnd);
String oldVersion = versionContainer.getBungeeVersion(); String oldVersion = versionContainer.getBungeeVersion();
//The file is already the newest version //The file is already the newest version
if (filePath.isFile() && newestVersion.equals(oldVersion)) { if (filePath.isFile() && newestVersion.equals(oldVersion)) {

View File

@ -1,5 +1,10 @@
package net.knarcraft.minecraftserverlauncher.server.servertypes; 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 java.io.File; import java.io.File;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -31,7 +36,31 @@ public class CraftBukkit extends AbstractServerType {
String targetFile = this.getName() + version + ".jar"; String targetFile = this.getName() + version + ".jar";
String file = downloadURLPart + version + ".jar"; String file = downloadURLPart + version + ".jar";
File filePath = new File(folder + targetFile); File filePath = new File(folder + targetFile);
return filePath.isFile() || downloadFile(downloadURL + file, Paths.get(filePath.toURI()));
if (version.equals("Latest")) {
return filePath.isFile() || buildJar(folder);
} else {
return filePath.isFile() || downloadFile(downloadURL + file, Paths.get(filePath.toURI()));
}
}
/**
* Builds the necessary .jar file
*
* @param folder <p>The folder the build file should be moved to</p>
* @return <p>True if the build was successful</p>
*/
private boolean buildJar(String folder) {
String buildToolsDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" + File.separator +
"BuildTools" + File.separator;
GUI gui = ServerLauncherController.getInstance().getGUI();
JarBuilder jarBuilder = new JarBuilder(buildToolsDirectory, folder, gui);
if (this instanceof Spigot) {
return jarBuilder.buildSpigotJar();
} else {
return jarBuilder.buildBukkitJar();
}
} }
} }

View File

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

View File

@ -20,8 +20,8 @@ public class SpongeVanilla extends AbstractServerType {
private final String srcStart; private final String srcStart;
private final String srcEnd; private final String srcEnd;
private final String downloadURLPart; private final String downloadURLPart;
Function<String,String> oldVersionFunction; Function<String, String> oldVersionFunction;
BiConsumer<String,String> versionUpdateFunction; BiConsumer<String, String> versionUpdateFunction;
final ServerVersionContainer serverVersionContainer; final ServerVersionContainer serverVersionContainer;
/** /**
@ -53,7 +53,7 @@ public class SpongeVanilla extends AbstractServerType {
String file = this.getName() + version + ".jar"; String file = this.getName() + version + ".jar";
File filePath = new File(folder + file); File filePath = new File(folder + file);
String versionText = CommonFunctions.readFile(versionURL + version); String versionText = CommonFunctions.readRemoteFile(versionURL + version);
String newestVersion = CommonFunctions.stringBetween(versionText, srcStart, srcEnd); String newestVersion = CommonFunctions.stringBetween(versionText, srcStart, srcEnd);
String jarURL = downloadURL + newestVersion + downloadURLPart + newestVersion + ".jar"; String jarURL = downloadURL + newestVersion + downloadURLPart + newestVersion + ".jar";

View File

@ -12,7 +12,7 @@ import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readRemoteFile;
/** /**
* This class represents the regular vanilla Minecraft server type * This class represents the regular vanilla Minecraft server type
@ -54,9 +54,9 @@ public class Vanilla extends AbstractServerType {
/** /**
* Downloads the latest .jar file found if necessary * Downloads the latest .jar file found if necessary
* *
* @param filePath <p>The path of the jar file to download</p> * @param filePath <p>The path of the jar file to download</p>
* @param releaseType <p>The release type used for downloading</p> * @param releaseType <p>The release type used for downloading</p>
* @param lastVersion <p>The last server version found</p> * @param lastVersion <p>The last server version found</p>
* @param versionContainer <p>The version container to use</p> * @param versionContainer <p>The version container to use</p>
* @return <p>True if the jar exists and is the latest version or was downloaded</p> * @return <p>True if the jar exists and is the latest version or was downloaded</p>
* @throws IOException <p>If the .jar cannot be downloaded</p> * @throws IOException <p>If the .jar cannot be downloaded</p>
@ -90,7 +90,7 @@ public class Vanilla extends AbstractServerType {
* @throws IOException <p>If the remote resource cannot be readFromServer</p> * @throws IOException <p>If the remote resource cannot be readFromServer</p>
*/ */
private String[] getLatestFile(String releaseType) throws IOException { private String[] getLatestFile(String releaseType) throws IOException {
String versionText = readFile(versionURL); String versionText = readRemoteFile(versionURL);
JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject();
String latest = jsonObject.getAsJsonObject("latest").get(releaseType).getAsString(); String latest = jsonObject.getAsJsonObject("latest").get(releaseType).getAsString();
String versionURL = getServerFileVersionURL(latest); String versionURL = getServerFileVersionURL(latest);
@ -106,7 +106,7 @@ public class Vanilla extends AbstractServerType {
* @throws IOException <p>If the remote resource cannot be readFromServer</p> * @throws IOException <p>If the remote resource cannot be readFromServer</p>
*/ */
private String getVanillaDownloadURL(String versionURL) throws IOException { private String getVanillaDownloadURL(String versionURL) throws IOException {
String versionText = readFile(versionURL); String versionText = readRemoteFile(versionURL);
JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject();
return jsonObject.getAsJsonObject("downloads").getAsJsonObject("server").get("url").getAsString(); return jsonObject.getAsJsonObject("downloads").getAsJsonObject("server").get("url").getAsString();
} }
@ -119,7 +119,7 @@ public class Vanilla extends AbstractServerType {
* @throws IOException <p>If the file cannot be downloaded</p> * @throws IOException <p>If the file cannot be downloaded</p>
*/ */
private String getServerFileVersionURL(String targetVersion) throws IOException { private String getServerFileVersionURL(String targetVersion) throws IOException {
String versionText = readFile(versionURL); String versionText = readRemoteFile(versionURL);
JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject(); JsonObject jsonObject = new JsonParser().parse(versionText).getAsJsonObject();
JsonArray availableVersions = jsonObject.getAsJsonArray("versions"); JsonArray availableVersions = jsonObject.getAsJsonArray("versions");
for (JsonElement availableVersion : availableVersions) { for (JsonElement availableVersion : availableVersions) {

View File

@ -9,7 +9,7 @@ import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readRemoteFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween;
/** /**
@ -50,13 +50,21 @@ public class Waterfall extends AbstractServerType {
public boolean downloadJar(String folder, String version) throws IOException { public boolean downloadJar(String folder, String version) throws IOException {
String file = this.getName() + version + ".jar"; String file = this.getName() + version + ".jar";
File filePath = new File(folder + file); File filePath = new File(folder + file);
String newestVersion = stringBetween(readFile(versionURL + version), srcStart, srcEnd); String versionFileContents = readRemoteFile(versionURL + version);
String fullURL = downloadURL + version + "/" + newestVersion + "/download"; String[] versions = stringBetween(versionFileContents, srcStart, srcEnd).split(",");
String newestVersion = versions[versions.length - 1];
String oldVersion = oldVersionFunction.apply(version); String oldVersion = oldVersionFunction.apply(version);
//The file is already the newest version //The file is already the newest version
if (filePath.isFile() && newestVersion.equals(oldVersion)) { if (filePath.isFile() && newestVersion.equals(oldVersion)) {
return true; 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 //The new jar file could not be downloaded
if (!downloadFile(fullURL, Paths.get(filePath.toURI()))) { if (!downloadFile(fullURL, Paths.get(filePath.toURI()))) {
return false; return false;
@ -64,4 +72,5 @@ public class Waterfall extends AbstractServerType {
versionUpdateFunction.accept(version, newestVersion); versionUpdateFunction.accept(version, newestVersion);
return true; 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

@ -5,7 +5,11 @@ import net.knarcraft.minecraftserverlauncher.Main;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.DefaultCaret; import javax.swing.text.DefaultCaret;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList; import java.util.ArrayList;
import static javax.swing.text.DefaultCaret.ALWAYS_UPDATE; import static javax.swing.text.DefaultCaret.ALWAYS_UPDATE;
@ -26,11 +30,12 @@ public class Console extends KeyAdapter implements ActionListener, KeyListener {
private final JPanel panel; private final JPanel panel;
private final ArrayList<String> commands = new ArrayList<>(); private final ArrayList<String> commands = new ArrayList<>();
private int commandIndex; private int commandIndex;
private final int maxConsoleLines = 1000;
/** /**
* Instantiates a new console * Instantiates a new console
* *
* @param tab <p>The tabbed pane used for displaying the console</p> * @param tab <p>The tabbed pane used for displaying the console</p>
* @param name <p>The name of the console tab</p> * @param name <p>The name of the console tab</p>
*/ */
Console(JTabbedPane tab, String name) { Console(JTabbedPane tab, String name) {
@ -67,9 +72,33 @@ public class Console extends KeyAdapter implements ActionListener, KeyListener {
* @param text <p>The string to print</p> * @param text <p>The string to print</p>
*/ */
public void output(String text) { public void output(String text) {
int outputLines = this.textOutput.getLineCount();
if (outputLines > maxConsoleLines) {
truncateConsole(outputLines);
}
this.textOutput.setText(this.textOutput.getText() + "\n" + text); this.textOutput.setText(this.textOutput.getText() + "\n" + text);
} }
/**
* Truncates the first 50 lines if the console output has reached the max limit
*
* @param outputLines <p>The currently readable lines in the console output field</p>
*/
private void truncateConsole(int outputLines) {
String oldText = this.textOutput.getText();
String[] oldTextList = oldText.split("\n");
for (int i = 0; i < outputLines - maxConsoleLines + 50; i++) {
oldTextList[i] = "";
}
StringBuilder newTextBuilder = new StringBuilder();
for (String line : oldTextList) {
if (!line.equals("")) {
newTextBuilder.append(line).append("\n");
}
}
this.textOutput.setText(newTextBuilder.toString());
}
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
//Sends the command from the input to the server with the same name. //Sends the command from the input to the server with the same name.
@ -99,7 +128,7 @@ public class Console extends KeyAdapter implements ActionListener, KeyListener {
* Shows the previously executed command in the input field * Shows the previously executed command in the input field
* *
* <p>Shows the previously executed command if a command was just executed. * <p>Shows the previously executed command if a command was just executed.
* Shows the command executed earlier if already showing a previously executed command.</p> * Shows the command executed earlier if already showing a previously executed command</p>
*/ */
private void showPreviousCommand() { private void showPreviousCommand() {
if (commands.size() > 0 && commandIndex > 0) { if (commands.size() > 0 && commandIndex > 0) {
@ -111,7 +140,7 @@ public class Console extends KeyAdapter implements ActionListener, KeyListener {
* Shows the next previously executed command or clears the input field * Shows the next previously executed command or clears the input field
* *
* <p>Shows the next previously executed command if such a command exists. * <p>Shows the next previously executed command if such a command exists.
* Clears the input field if no next used command exists.</p> * Clears the input field if no next used command exists</p>
*/ */
private void showNextCommand() { private void showNextCommand() {
if (commands.size() > 0) { if (commands.size() > 0) {

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

@ -44,6 +44,20 @@ public interface GUI {
*/ */
void showMessage(String message); void showMessage(String message);
/**
* Logs a message to the logfile
*
* @param message <p>The message to log</p>
*/
void logMessage(String message);
/**
* Logs an error to the logfile
*
* @param error <p>The error to log</p>
*/
void logError(String error);
/** /**
* Asks the user for a directory as a file object * Asks the user for a directory as a file object
* *

View File

@ -1,6 +1,13 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.swing.*; import javax.swing.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
/** /**
* This class handles displaying messages to the user * This class handles displaying messages to the user
@ -8,6 +15,8 @@ import javax.swing.*;
public abstract class MessageHandler implements GUI { public abstract class MessageHandler implements GUI {
private final boolean silent; private final boolean silent;
private final BufferedWriter writer;
private final String logFile = Main.getApplicationWorkDirectory() + File.separator + "latestrun.log";
/*** /***
* Initializes a new message handler * Initializes a new message handler
@ -16,15 +25,42 @@ public abstract class MessageHandler implements GUI {
*/ */
public MessageHandler(boolean silent) { public MessageHandler(boolean silent) {
this.silent = silent; this.silent = silent;
this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
}
@Override
public void logMessage(String message) {
try {
CommonFunctions.appendFile(logFile, "[Info]: " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void logError(String message) {
try {
CommonFunctions.appendFile(logFile, "[Error]: " + message);
} catch (IOException e) {
e.printStackTrace();
}
} }
@Override @Override
public void showError(String title, String message) { public void showError(String title, String message) {
if (silent) { if (silent) {
System.out.println(message); try {
writer.write("[Error]: ");
writer.write(message);
writer.newLine();
writer.flush();
} catch (IOException e) {
System.out.println(message);
}
} else { } else {
showJOptionPane(title, message, JOptionPane.ERROR_MESSAGE); showJOptionPane(title, message, JOptionPane.ERROR_MESSAGE);
} }
logError(message);
} }
@Override @Override
@ -35,10 +71,17 @@ public abstract class MessageHandler implements GUI {
@Override @Override
public void showMessage(String title, String message) { public void showMessage(String title, String message) {
if (silent) { if (silent) {
System.out.println(message); try {
writer.write("[Info]: " + message);
writer.newLine();
writer.flush();
} catch (IOException e) {
System.out.println(message);
}
} else { } else {
showJOptionPane(title, message, JOptionPane.INFORMATION_MESSAGE); showJOptionPane(title, message, JOptionPane.INFORMATION_MESSAGE);
} }
logMessage(message);
} }
@Override @Override

View File

@ -1,7 +1,11 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.IOException;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsStream;
/** /**
* This class keeps track of all consoles * This class keeps track of all consoles
@ -17,15 +21,27 @@ public class ServerConsoles {
private static JFrame frame; private static JFrame frame;
private static JTabbedPane consolesTabbedPane; private static JTabbedPane consolesTabbedPane;
private ServerConsoles() {
}
/** /**
* Initializes the server consoles frame * Initializes the server consoles frame
*/ */
public ServerConsoles() { public static void instantiate() {
frame = new JFrame(); if (frame == null || consolesTabbedPane == null) {
frame.setBounds(100, 100, 450, 300); frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); frame.setBounds(100, 100, 450, 300);
consolesTabbedPane = new JTabbedPane(JTabbedPane.TOP); frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
frame.getContentPane().add(consolesTabbedPane, BorderLayout.CENTER); 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

@ -1,7 +1,7 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.Controller; import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import javax.swing.*; import javax.swing.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -22,16 +22,15 @@ public class ServerControlTab implements ActionListener {
private JButton customCommandButton; private JButton customCommandButton;
private JButton saveServerButton; private JButton saveServerButton;
private JButton reloadButton; private JButton reloadButton;
private JButton showConsolesButton;
private JTextField customCommandTextField; private JTextField customCommandTextField;
private Controller controller = Controller.getInstance(); private final ServerLauncherController controller = ServerLauncherController.getInstance();
private final ArrayList<String> globalPlayers; private final ArrayList<String> globalPlayers;
/** /**
* Instantiates a new server control tab * Instantiates a new server control tab
* *
* @param mainFrame <p>The main frame of the GUI</p> * @param mainFrame <p>The main frame of the GUI</p>
* @param controlServers <p>The JPanel to attach the server controls to</p> * @param controlServers <p>The JPanel to attach the server controls to</p>
*/ */
public ServerControlTab(JFrame mainFrame, JPanel controlServers) { public ServerControlTab(JFrame mainFrame, JPanel controlServers) {
@ -42,7 +41,7 @@ public class ServerControlTab implements ActionListener {
/** /**
* Initializes GUI elements for the server control tab * Initializes GUI elements for the server control tab
* *
* @param mainFrame <p>The main frame of the GUI</p> * @param mainFrame <p>The main frame of the GUI</p>
* @param controlServers <p>The JPanel to attach the server controls to</p> * @param controlServers <p>The JPanel to attach the server controls to</p>
*/ */
private void initialize(JFrame mainFrame, JPanel controlServers) { private void initialize(JFrame mainFrame, JPanel controlServers) {
@ -134,13 +133,6 @@ public class ServerControlTab implements ActionListener {
springLayout.putConstraint(SpringLayout.EAST, reloadButton, 0, SpringLayout.EAST, opButton); springLayout.putConstraint(SpringLayout.EAST, reloadButton, 0, SpringLayout.EAST, opButton);
controlServers.add(reloadButton); controlServers.add(reloadButton);
reloadButton.addActionListener(this); 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);
} }
/** /**
@ -149,7 +141,7 @@ public class ServerControlTab implements ActionListener {
public void update() { public void update() {
this.targetServerCombo.removeAllItems(); this.targetServerCombo.removeAllItems();
this.targetServerCombo.addItem("All"); this.targetServerCombo.addItem("All");
for (Collection collection : Controller.getInstance().getCurrentProfile().getCollections()) { for (Collection collection : ServerLauncherController.getInstance().getCurrentProfile().getCollections()) {
this.targetServerCombo.addItem(collection.getName()); this.targetServerCombo.addItem(collection.getName());
} }
} }
@ -181,9 +173,7 @@ public class ServerControlTab implements ActionListener {
//Registers actions on all commands executed on a specific server //Registers actions on all commands executed on a specific server
handleServerCommands(actionSource, selectedServerValue); handleServerCommands(actionSource, selectedServerValue);
if (actionSource == showConsolesButton) { if (actionSource == targetServerCombo) {
ServerConsoles.setAsVisible();
} else if (actionSource == targetServerCombo) {
updatePlayers(); updatePlayers();
} }
} }
@ -232,7 +222,7 @@ public class ServerControlTab implements ActionListener {
/** /**
* Handles command buttons acting on a specific server * Handles command buttons acting on a specific server
* *
* @param actionSource <p>The object being interacted with</p> * @param actionSource <p>The object being interacted with</p>
* @param selectedServerValue <p>The server currently selected</p> * @param selectedServerValue <p>The server currently selected</p>
*/ */
private void handleServerCommands(Object actionSource, String selectedServerValue) { private void handleServerCommands(Object actionSource, String selectedServerValue) {
@ -252,7 +242,7 @@ public class ServerControlTab implements ActionListener {
/** /**
* Handles command buttons which act on a player * Handles command buttons which act on a player
* *
* @param actionSource <p>The clicked object</p> * @param actionSource <p>The clicked object</p>
* @param selectedServerValue <p>The server currently selected</p> * @param selectedServerValue <p>The server currently selected</p>
* @param selectedPlayerValue <p>The player currently selected</p> * @param selectedPlayerValue <p>The player currently selected</p>
*/ */

View File

@ -2,25 +2,22 @@ package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Collection; import net.knarcraft.minecraftserverlauncher.profile.Collection;
import net.knarcraft.minecraftserverlauncher.profile.Controller; import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.server.Server;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import javax.swing.*; import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonUI;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.io.File;
import java.awt.event.MouseEvent; import java.io.FileNotFoundException;
import java.awt.event.WindowAdapter; import java.io.IOException;
import java.awt.event.WindowEvent; import java.util.HashMap;
import java.io.*; import java.util.Map;
import java.util.*; import java.util.Scanner;
import java.util.concurrent.Executors;
import static java.awt.Frame.NORMAL;
import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE; import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsScanner; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsScanner;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsStream; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsStream;
@ -34,25 +31,19 @@ import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getR
*/ */
public class ServerLauncherGUI extends MessageHandler implements ActionListener, GUI { public class ServerLauncherGUI extends MessageHandler implements ActionListener, GUI {
private final JLabel lblStatuslabel = new JLabel("Servers are stopped"); private final ServerLauncherController controller;
private Controller controller;
private Map<String, String> textStrings; private Map<String, String> textStrings;
private Tray applicationTray; private Tray applicationTray;
private JFrame frame; private JFrame frame;
private JTabbedPane tabbedPane; private JTabbedPane mainTabbedPane;
private JTabbedPane serversPane; private JTabbedPane serversPane;
private ServerControlTab serverControlTab; private ServerControlTab serverControlTab;
private ControlPanelTab controlPanelTab;
private ServerLauncherMenu serverLauncherMenu; private ServerLauncherMenu serverLauncherMenu;
//Basic controls private JButton addServerTabButton;
private JButton startServerButton; private JButton addServerPaneButton;
private JButton stopServerButton;
private JButton addServerButton;
private JButton backupButton;
private JButton addProfileButton;
private JButton deleteProfileButton;
private JComboBox<String> profiles;
/** /**
* Creates the application window * Creates the application window
@ -64,6 +55,20 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
this.controller = Main.getController(); this.controller = Main.getController();
} }
/**
* Creates the application window
*
* @param silent <p>Whether to make the GUI silent (hidden, for testing)</p>
*/
public ServerLauncherGUI(boolean silent) throws IOException {
super(silent);
if (!silent) {
initialize(440, 170);
}
loadMessages();
this.controller = Main.getController();
}
/** /**
* Creates the application window with a preferred width and height * Creates the application window with a preferred width and height
* *
@ -86,15 +91,19 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
return this.serversPane; 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 @Override
public void setStatus(String text) { public void setStatus(String text) {
this.lblStatuslabel.setText(text); controlPanelTab.setStatusText(text);
try (PrintWriter file = new PrintWriter(new FileWriter(Main.getApplicationWorkDirectory() + this.logMessage(text);
File.separator + "latestrun.log", true))) {
file.println(text);
} catch (IOException e) {
e.printStackTrace();
}
} }
@Override @Override
@ -112,18 +121,6 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
return chooser.getSelectedFile(); 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 * Gets the server control tab used by this GUI
* *
@ -146,19 +143,94 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
* Updates ServerLauncherGUI according to current profile settings * Updates ServerLauncherGUI according to current profile settings
*/ */
public void updateWithSavedProfileData() { public void updateWithSavedProfileData() {
Controller controller = Main.getController(); ServerLauncherController controller = Main.getController();
serversPane.removeAll(); serversPane.removeAll();
serverLauncherMenu.update(); serverLauncherMenu.update();
serverControlTab.update(); serverControlTab.update();
for (Collection collection : controller.getCurrentProfile().getCollections()) { for (Collection collection : controller.getCurrentProfile().getCollections()) {
serversPane.addTab(collection.getName(), collection.getServerTab().getPanel()); 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));
} }
/** /**
* Initializes the server launcher GUI * Initializes the server launcher GUI
* *
* @param width <p>The width of the GUI</p> * @param width <p>The width of the GUI</p>
* @param height <p>The height of the GUI</p> * @param height <p>The height of the GUI</p>
* @throws IOException <p>If unable to load the GUI icon</p> * @throws IOException <p>If unable to load the GUI icon</p>
*/ */
@ -169,7 +241,7 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
UnsupportedLookAndFeelException | UnsupportedLookAndFeelException |
InstantiationException | InstantiationException |
IllegalAccessException e IllegalAccessException e
) { ) {
e.printStackTrace(); e.printStackTrace();
} }
@ -186,93 +258,30 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
this.serverLauncherMenu = new ServerLauncherMenu(menuBar, this); this.serverLauncherMenu = new ServerLauncherMenu(menuBar, this);
tabbedPane = new JTabbedPane(JTabbedPane.TOP); mainTabbedPane = new JTabbedPane(JTabbedPane.TOP);
frame.getContentPane().add(tabbedPane); frame.getContentPane().add(mainTabbedPane);
JPanel panelBasic = new JPanel(); JPanel controlPanelPanel = new JPanel();
tabbedPane.addTab("Control panel", null, panelBasic, null); mainTabbedPane.addTab("Control panel", null, controlPanelPanel, null);
SpringLayout sl_panel = new SpringLayout(); controlPanelTab = new ControlPanelTab(controlPanelPanel);
panelBasic.setLayout(sl_panel);
JLabel lblBasicControls = new JLabel("Basic controls"); JPanel controlServersPanel = new JPanel();
sl_panel.putConstraint(SpringLayout.NORTH, lblBasicControls, 10, SpringLayout.NORTH, panelBasic); mainTabbedPane.addTab("Control servers", null, controlServersPanel, null);
panelBasic.add(lblBasicControls); serverControlTab = new ServerControlTab(frame, controlServersPanel);
startServerButton = new JButton("Start servers"); JPanel serversPanel = new JPanel();
sl_panel.putConstraint(SpringLayout.WEST, lblBasicControls, 0, SpringLayout.WEST, startServerButton); mainTabbedPane.addTab("Servers", null, serversPanel, null);
sl_panel.putConstraint(SpringLayout.NORTH, startServerButton, 6, SpringLayout.SOUTH, lblBasicControls); SpringLayout serversPanelSpringLayout = new SpringLayout();
sl_panel.putConstraint(SpringLayout.WEST, startServerButton, 10, SpringLayout.WEST, panelBasic); serversPanel.setLayout(serversPanelSpringLayout);
panelBasic.add(startServerButton);
startServerButton.addActionListener(this);
stopServerButton = new JButton("Stop servers"); serversPane = new JTabbedPane(JTabbedPane.TOP);
sl_panel.putConstraint(SpringLayout.NORTH, stopServerButton, 0, SpringLayout.NORTH, startServerButton); serversPanelSpringLayout.putConstraint(SpringLayout.NORTH, serversPane, 0, SpringLayout.NORTH, serversPanel);
sl_panel.putConstraint(SpringLayout.WEST, stopServerButton, 6, SpringLayout.EAST, startServerButton); serversPanelSpringLayout.putConstraint(SpringLayout.WEST, serversPane, 0, SpringLayout.WEST, serversPanel);
panelBasic.add(stopServerButton); serversPanelSpringLayout.putConstraint(SpringLayout.SOUTH, serversPane, 0, SpringLayout.SOUTH, serversPanel);
stopServerButton.addActionListener(this); serversPanelSpringLayout.putConstraint(SpringLayout.EAST, serversPane, 0, SpringLayout.EAST, serversPanel);
serversPanel.add(serversPane);
JLabel lblProfile = new JLabel("Profile"); serversPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
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);
frame.validate(); frame.validate();
frame.pack(); frame.pack();
@ -288,63 +297,22 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
applicationTray.hideToTray(); 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 @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
Object actionSource = e.getSource(); Object actionSource = e.getSource();
//Registers actions on the main tab //Register actions on the main tab
handleMainTabButtons(actionSource); handleMainTabButtons(actionSource);
} }
/** /**
* Handles buttons and the combo on the main tab * Handles buttons and the combo on the main tab
* *
* @param actionSource <p>The object being interacted with</p> * @param actionSource <p>The object being interacted with</p>
*/ */
private void handleMainTabButtons(Object actionSource) { private void handleMainTabButtons(Object actionSource) {
if (actionSource == startServerButton) { if (actionSource == addServerTabButton || actionSource == addServerPaneButton) {
controller.saveState();
Executors.newSingleThreadExecutor().execute(Server::startServers);
} else if (actionSource == stopServerButton) {
stopServers();
} else if (actionSource == addServerButton) {
addServer(); 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();
} }
} }
@ -369,39 +337,9 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
*/ */
public void updateGUIElementsWhenServersStartOrStop(boolean running) { public void updateGUIElementsWhenServersStartOrStop(boolean running) {
boolean stopped = !running; //Most gui is only enabled when the server is stopped rather than running. boolean stopped = !running; //Most gui is only enabled when the server is stopped rather than running.
profiles.setEnabled(stopped); mainTabbedPane.setEnabledAt(1, !stopped);
addProfileButton.setEnabled(stopped); mainTabbedPane.setEnabledAt(2, stopped);
deleteProfileButton.setEnabled(stopped); controlPanelTab.updateGUIElementsWhenServersStartOrStop(running);
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();
}
} }
/** /**

View File

@ -1,12 +1,13 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.profile.Controller;
import net.knarcraft.minecraftserverlauncher.profile.Profile; import net.knarcraft.minecraftserverlauncher.profile.Profile;
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.swing.*; import javax.swing.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.File;
import java.util.Objects; import java.util.Objects;
/** /**
@ -14,11 +15,14 @@ import java.util.Objects;
*/ */
public class ServerLauncherMenu implements ActionListener { public class ServerLauncherMenu implements ActionListener {
private JMenuBar menuBar; private final JMenuBar menuBar;
//Options //Options
private JCheckBoxMenuItem runInBackgroundCheckBoxMenuItem; private JCheckBoxMenuItem runInBackgroundCheckBoxMenuItem;
private JCheckBoxMenuItem delayStartupCheckBoxMenuItem; private JCheckBoxMenuItem delayStartupCheckBoxMenuItem;
private JCheckBoxMenuItem downloadJarsCheckBoxMenuItem; private JCheckBoxMenuItem downloadJarsCheckBoxMenuItem;
private JMenuItem javaCommandMenuItem;
private JMenuItem oldJavaCommandMenuItem;
private JMenuItem deleteBuiltJarsMenuItem;
//Help //Help
private JMenuItem errorsMenuItem; private JMenuItem errorsMenuItem;
private JMenuItem setupMenuItem; private JMenuItem setupMenuItem;
@ -27,21 +31,24 @@ public class ServerLauncherMenu implements ActionListener {
private JMenuItem runInBackgroundMenuItem; private JMenuItem runInBackgroundMenuItem;
private JMenuItem delayStartupMenuItem; private JMenuItem delayStartupMenuItem;
private JMenuItem downloadJarsMenuItem; private JMenuItem downloadJarsMenuItem;
private JMenuItem javaCommandInfoMenuItem;
private JMenuItem oldJavaCommandInfoMenuItem;
private JMenuItem deleteBuiltJarsInfoMenuItem;
//Info/about //Info/about
private JMenuItem aboutMenuItem; private JMenuItem aboutMenuItem;
private JMenuItem storyMenuItem; private JMenuItem storyMenuItem;
private Controller controller; private final ServerLauncherController controller;
private ServerLauncherGUI serverLauncherGUI; private final ServerLauncherGUI serverLauncherGUI;
/** /**
* Initializes a new server launcher menu * Initializes a new server launcher menu
* *
* @param menuBar <p>The menu bar to attach items to</p> * @param menuBar <p>The menu bar to attach items to</p>
* @param serverLauncherGUI <p>The server launcher GUI to use</p> * @param serverLauncherGUI <p>The server launcher GUI to use</p>
*/ */
public ServerLauncherMenu(JMenuBar menuBar, ServerLauncherGUI serverLauncherGUI) { public ServerLauncherMenu(JMenuBar menuBar, ServerLauncherGUI serverLauncherGUI) {
this.controller = Controller.getInstance(); this.controller = ServerLauncherController.getInstance();
this.menuBar = menuBar; this.menuBar = menuBar;
this.serverLauncherGUI = serverLauncherGUI; this.serverLauncherGUI = serverLauncherGUI;
initialize(); initialize();
@ -53,64 +60,23 @@ public class ServerLauncherMenu implements ActionListener {
public void initialize() { public void initialize() {
JMenu mnOptions = new JMenu("Options"); JMenu mnOptions = new JMenu("Options");
menuBar.add(mnOptions); menuBar.add(mnOptions);
generateOptionsMenuItems(mnOptions);
runInBackgroundCheckBoxMenuItem = new JCheckBoxMenuItem("Run in background on exit");
mnOptions.add(runInBackgroundCheckBoxMenuItem);
runInBackgroundCheckBoxMenuItem.addActionListener(this);
delayStartupCheckBoxMenuItem = new JCheckBoxMenuItem("Delay Startup");
mnOptions.add(delayStartupCheckBoxMenuItem);
delayStartupCheckBoxMenuItem.addActionListener(this);
downloadJarsCheckBoxMenuItem = new JCheckBoxMenuItem("Download jars");
mnOptions.add(downloadJarsCheckBoxMenuItem);
downloadJarsCheckBoxMenuItem.addActionListener(this);
JMenu mnHelp = new JMenu("Help"); JMenu mnHelp = new JMenu("Help");
menuBar.add(mnHelp); menuBar.add(mnHelp);
errorsMenuItem = new JMenuItem("Errors"); errorsMenuItem = createMenuItem("Errors", mnHelp);
mnHelp.add(errorsMenuItem); setupMenuItem = createMenuItem("Setup", mnHelp);
errorsMenuItem.addActionListener(this); manualUpdateMenuItem = createMenuItem("Manual update", mnHelp);
setupMenuItem = new JMenuItem("Setup");
mnHelp.add(setupMenuItem);
setupMenuItem.addActionListener(this);
manualUpdateMenuItem = new JMenuItem("Manual update");
mnHelp.add(manualUpdateMenuItem);
manualUpdateMenuItem.addActionListener(this);
JMenu mnInfo = new JMenu("Info"); JMenu mnInfo = new JMenu("Info");
menuBar.add(mnInfo); menuBar.add(mnInfo);
generateInfoMenuItems(mnInfo);
JMenu mnOptionsInfo = new JMenu("Options");
mnInfo.add(mnOptionsInfo);
runInBackgroundMenuItem = new JMenuItem("Run in background on exit");
mnOptionsInfo.add(runInBackgroundMenuItem);
runInBackgroundMenuItem.addActionListener(this);
delayStartupMenuItem = new JMenuItem("Delay Startup");
mnOptionsInfo.add(delayStartupMenuItem);
delayStartupMenuItem.addActionListener(this);
downloadJarsMenuItem = new JMenuItem("Download jars");
mnOptionsInfo.add(downloadJarsMenuItem);
downloadJarsMenuItem.addActionListener(this);
JMenu mnAbout = new JMenu("About");
mnInfo.add(mnAbout);
aboutMenuItem = new JMenuItem("About");
mnAbout.add(aboutMenuItem);
aboutMenuItem.addActionListener(this);
storyMenuItem = new JMenuItem("Story");
mnAbout.add(storyMenuItem);
storyMenuItem.addActionListener(this);
} }
/**
* Updates the state of checkboxes based on whether options are enabled
*/
public void update() { public void update() {
runInBackgroundCheckBoxMenuItem.setState(controller.getRunInBackground()); runInBackgroundCheckBoxMenuItem.setState(controller.getRunInBackground());
delayStartupCheckBoxMenuItem.setState(controller.getDelayStartup() > 0); delayStartupCheckBoxMenuItem.setState(controller.getDelayStartup() > 0);
@ -126,6 +92,12 @@ public class ServerLauncherMenu implements ActionListener {
delay(); delay();
} else if (actionSource == downloadJarsCheckBoxMenuItem) { } else if (actionSource == downloadJarsCheckBoxMenuItem) {
downloadJars(); downloadJars();
} else if (actionSource == javaCommandMenuItem) {
configureJava(false);
} else if (actionSource == oldJavaCommandMenuItem) {
configureJava(true);
} else if (actionSource == deleteBuiltJarsMenuItem) {
deleteBuiltJars();
} else if (actionSource == errorsMenuItem) { } else if (actionSource == errorsMenuItem) {
CommonFunctions.goToURL(serverLauncherGUI.getMessage("infoURL")); CommonFunctions.goToURL(serverLauncherGUI.getMessage("infoURL"));
} else if (actionSource == setupMenuItem) { } else if (actionSource == setupMenuItem) {
@ -138,6 +110,12 @@ public class ServerLauncherMenu implements ActionListener {
serverLauncherGUI.showMessage("Delay startup", serverLauncherGUI.getMessage("delayStartupText")); serverLauncherGUI.showMessage("Delay startup", serverLauncherGUI.getMessage("delayStartupText"));
} else if (actionSource == downloadJarsMenuItem) { } else if (actionSource == downloadJarsMenuItem) {
serverLauncherGUI.showMessage("Download jars", serverLauncherGUI.getMessage("downloadJarsText")); serverLauncherGUI.showMessage("Download jars", serverLauncherGUI.getMessage("downloadJarsText"));
} else if (actionSource == javaCommandInfoMenuItem) {
serverLauncherGUI.showMessage("Java command", serverLauncherGUI.getMessage("javaCommandText"));
} else if (actionSource == oldJavaCommandInfoMenuItem) {
serverLauncherGUI.showMessage("Old Java command", serverLauncherGUI.getMessage("oldJavaCommandText"));
} else if (actionSource == deleteBuiltJarsInfoMenuItem) {
serverLauncherGUI.showMessage("Delete built jar files", serverLauncherGUI.getMessage("deleteBuiltJarFilesText"));
} else if (actionSource == aboutMenuItem) { } else if (actionSource == aboutMenuItem) {
serverLauncherGUI.showMessage("About", serverLauncherGUI.getMessage("aboutText")); serverLauncherGUI.showMessage("About", serverLauncherGUI.getMessage("aboutText"));
} else if (actionSource == storyMenuItem) { } else if (actionSource == storyMenuItem) {
@ -145,11 +123,122 @@ public class ServerLauncherMenu implements ActionListener {
} }
} }
/**
* Generates the children of the options menu
*
* @param mnOptions <p>A reference to the options menu</p>
*/
private void generateOptionsMenuItems(JMenu mnOptions) {
runInBackgroundCheckBoxMenuItem = createCheckBoxMenuItem("Run in background on exit", mnOptions);
delayStartupCheckBoxMenuItem = createCheckBoxMenuItem("Delay Startup", mnOptions);
downloadJarsCheckBoxMenuItem = createCheckBoxMenuItem("Download jars", mnOptions);
javaCommandMenuItem = createMenuItem("Java command", mnOptions);
oldJavaCommandMenuItem = createMenuItem("Old Java command", mnOptions);
deleteBuiltJarsMenuItem = createMenuItem("Delete built jar files", mnOptions);
}
/**
* Generates the children of the info menu
*
* @param mnInfo <p>A reference to the info menu</p>
*/
private void generateInfoMenuItems(JMenu mnInfo) {
JMenu mnOptionsInfo = new JMenu("Options");
mnInfo.add(mnOptionsInfo);
runInBackgroundMenuItem = createMenuItem("Run in background on exit", mnOptionsInfo);
delayStartupMenuItem = createMenuItem("Delay Startup", mnOptionsInfo);
downloadJarsMenuItem = createMenuItem("Download jars", mnOptionsInfo);
javaCommandInfoMenuItem = createMenuItem("Java command", mnOptionsInfo);
oldJavaCommandInfoMenuItem = createMenuItem("Old Java command", mnOptionsInfo);
deleteBuiltJarsInfoMenuItem = createMenuItem("Delete built jar files", mnOptionsInfo);
JMenu mnAbout = new JMenu("About");
mnInfo.add(mnAbout);
aboutMenuItem = createMenuItem("About", mnAbout);
storyMenuItem = createMenuItem("Story", mnAbout);
}
/**
* Creates a checkbox menu item
*
* @param itemName <p>The name of the new checkbox item</p>
* @param parent <p>The parent menu the item belongs to</p>
* @return <p>The created checkbox menu item</p>
*/
private JCheckBoxMenuItem createCheckBoxMenuItem(String itemName, JMenu parent) {
JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(itemName);
parent.add(menuItem);
menuItem.addActionListener(this);
return menuItem;
}
/**
* Creates a menu item
*
* @param itemName <p>The name of the new item</p>
* @param parent <p>The parent menu the item belongs to</p>
* @return <p>The created menu item</p>
*/
private JMenuItem createMenuItem(String itemName, JMenu parent) {
JMenuItem menuItem = new JMenuItem(itemName);
parent.add(menuItem);
menuItem.addActionListener(this);
return menuItem;
}
/**
* 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) {
if (old) {
String response = JOptionPane.showInputDialog("Command or path to Java: ", controller.getOldJavaCommand());
if (response != null) {
controller.setOldJavaCommand(response);
}
} else {
String response = JOptionPane.showInputDialog("Command or path to Java: ", controller.getJavaCommand());
if (response != null) {
controller.setJavaCommand(response);
}
}
}
/**
* Deletes build Spigot and Bukkit .jar files if the user accepts
*/
private void deleteBuiltJars() {
int answer = JOptionPane.showConfirmDialog(null, "This will delete built .jar files, causing them " +
"to be rebuilt on the next run. Do you want to continue?", "Delete built .jar files",
JOptionPane.YES_NO_OPTION
);
if (answer == JOptionPane.YES_NO_OPTION) {
String jarDirectory = controller.getJarDirectory();
File spigotFile = new File(jarDirectory + "SpigotLatest.jar");
File bukkitFile = new File(jarDirectory + "BukkitLatest.jar");
boolean success = true;
if (spigotFile.exists() && !spigotFile.delete()) {
serverLauncherGUI.showError("Unable to delete latest spigot .jar");
success = false;
}
if (bukkitFile.exists() && !bukkitFile.delete()) {
serverLauncherGUI.showError("Unable to delete latest bukkit .jar");
success = false;
}
if (success) {
serverLauncherGUI.showMessage("Deletion successful", "Deleted built .jar files");
}
}
}
/** /**
* Asks the user for a delay if checked, and sets the value to the current profile * Asks the user for a delay if checked, and sets the value to the current profile
*/ */
private void delay() { private void delay() {
String selectedProfile = serverLauncherGUI.getSelectedProfile(); String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) { if (selectedProfile != null) {
Profile profile = controller.getProfileByName(selectedProfile); Profile profile = controller.getProfileByName(selectedProfile);
if (delayStartupCheckBoxMenuItem.isSelected()) { if (delayStartupCheckBoxMenuItem.isSelected()) {
@ -172,7 +261,7 @@ public class ServerLauncherMenu implements ActionListener {
* Saves the runInBackground setting to the current profile * Saves the runInBackground setting to the current profile
*/ */
private void background() { private void background() {
String selectedProfile = serverLauncherGUI.getSelectedProfile(); String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) { if (selectedProfile != null) {
Profile profile = controller.getProfileByName(selectedProfile); Profile profile = controller.getProfileByName(selectedProfile);
Objects.requireNonNull(profile).setRunInBackground(runInBackgroundCheckBoxMenuItem.isSelected()); Objects.requireNonNull(profile).setRunInBackground(runInBackgroundCheckBoxMenuItem.isSelected());
@ -185,7 +274,7 @@ public class ServerLauncherMenu implements ActionListener {
* Saves the downloadJars setting to the current profile * Saves the downloadJars setting to the current profile
*/ */
private void downloadJars() { private void downloadJars() {
String selectedProfile = serverLauncherGUI.getSelectedProfile(); String selectedProfile = serverLauncherGUI.getControlPanelTab().getSelectedProfile();
if (selectedProfile != null) { if (selectedProfile != null) {
controller.setDownloadAllJars(downloadJarsCheckBoxMenuItem.isSelected()); controller.setDownloadAllJars(downloadJarsCheckBoxMenuItem.isSelected());
} else { } else {

View File

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

View File

@ -1,13 +1,17 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.profile.Controller; import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -20,14 +24,14 @@ public class Tray {
private SystemTray tray; private SystemTray tray;
private TrayIcon trayIcon; private TrayIcon trayIcon;
private Controller controller; private final ServerLauncherController controller;
private ServerLauncherGUI serverLauncherGUI; private final ServerLauncherGUI serverLauncherGUI;
private JFrame mainFrame; private final JFrame mainFrame;
/** /**
* Instantiates a new tray * Instantiates a new tray
* *
* @param mainFrame <p>The main frame of the GUI</p> * @param mainFrame <p>The main frame of the GUI</p>
* @param serverLauncherGUI <p>The server launcher GUI to use</p> * @param serverLauncherGUI <p>The server launcher GUI to use</p>
*/ */
public Tray(JFrame mainFrame, ServerLauncherGUI serverLauncherGUI) { public Tray(JFrame mainFrame, ServerLauncherGUI serverLauncherGUI) {
@ -55,7 +59,7 @@ public class Tray {
trayIcon = new TrayIcon(trayImage, "Minecraft Server Launcher", popup); trayIcon = new TrayIcon(trayImage, "Minecraft Server Launcher", popup);
trayIcon.setImageAutoSize(true); trayIcon.setImageAutoSize(true);
ActionListener exitListener = e -> { ActionListener exitListener = e -> {
serverLauncherGUI.stopServers(); serverLauncherGUI.getControlPanelTab().stopServers();
controller.saveState(); controller.saveState();
System.exit(0); System.exit(0);
}; };
@ -88,7 +92,7 @@ public class Tray {
e1.printStackTrace(); e1.printStackTrace();
} }
} else { } else {
serverLauncherGUI.stopServers(); serverLauncherGUI.getControlPanelTab().stopServers();
controller.saveState(); controller.saveState();
System.exit(0); System.exit(0);
} }
@ -110,7 +114,7 @@ public class Tray {
@Override @Override
public void windowClosing(WindowEvent e) { public void windowClosing(WindowEvent e) {
controller.saveState(); controller.saveState();
serverLauncherGUI.stopServers(); serverLauncherGUI.getControlPanelTab().stopServers();
System.exit(0); System.exit(0);
} }
}); });

View File

@ -12,7 +12,8 @@ public class WebBrowser {
private static JFrame browserFrame; private static JFrame browserFrame;
private static JTextPane editorPane; private static JTextPane editorPane;
private WebBrowser() {} private WebBrowser() {
}
/** /**
* Instantiates a new web browser * Instantiates a new web browser
@ -44,7 +45,8 @@ public class WebBrowser {
editorPane.setContentType("text/html"); editorPane.setContentType("text/html");
editorPane.addHyperlinkListener(hyperlinkEvent -> { editorPane.addHyperlinkListener(hyperlinkEvent -> {
if (hyperlinkEvent.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { if (hyperlinkEvent.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
displayPage(hyperlinkEvent.getURL().toString()); } displayPage(hyperlinkEvent.getURL().toString());
}
}); });
} catch (IOException e) { } catch (IOException e) {
editorPane.setContentType("text/html"); editorPane.setContentType("text/html");

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,12 +1,19 @@
package net.knarcraft.minecraftserverlauncher.utility; package net.knarcraft.minecraftserverlauncher.utility;
import net.knarcraft.minecraftserverlauncher.Main; 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 net.knarcraft.minecraftserverlauncher.userinterface.WebBrowser;
import java.io.*; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
@ -16,7 +23,7 @@ import java.nio.file.StandardCopyOption;
import java.util.Scanner; import java.util.Scanner;
/** /**
* A holding class for methods shared between classes. * A holding class for methods shared between classes
* *
* @author Kristian Knarvik <kristian.knarvik@knett.no> * @author Kristian Knarvik <kristian.knarvik@knett.no>
* @version 1.0.0 * @version 1.0.0
@ -34,7 +41,6 @@ public final class CommonFunctions {
public static void createAllFolders() throws FileNotFoundException { public static void createAllFolders() throws FileNotFoundException {
createFolder(new File(filesDirectory)); createFolder(new File(filesDirectory));
createFolder(new File(filesDirectory + File.separator + "Jars")); createFolder(new File(filesDirectory + File.separator + "Jars"));
createFolder(new File(filesDirectory + File.separator + "testjars"));
} }
/** /**
@ -43,7 +49,7 @@ public final class CommonFunctions {
* @param folder <p>The folder to create</p> * @param folder <p>The folder to create</p>
* @throws FileNotFoundException <p>If unable to create the folder</p> * @throws FileNotFoundException <p>If unable to create the folder</p>
*/ */
private static void createFolder(File folder) throws FileNotFoundException { public static void createFolder(File folder) throws FileNotFoundException {
if (!folder.exists()) { if (!folder.exists()) {
if (!folder.mkdirs()) { if (!folder.mkdirs()) {
throw new FileNotFoundException("Cannot create necessary directory."); throw new FileNotFoundException("Cannot create necessary directory.");
@ -100,12 +106,89 @@ public final class CommonFunctions {
* *
* @param path <p>The full url of the file to readFromServer</p> * @param path <p>The full url of the file to readFromServer</p>
* @return <p>True if successful. False otherwise</p> * @return <p>True if successful. False otherwise</p>
* @throws IOException <p>If unable to find or read the file</p>
*/ */
public static String readFile(String path) throws IOException { public static String readRemoteFile(String path) throws IOException {
URL url = new URL(path); URL url = new URL(path);
return new Scanner(url.openStream()).useDelimiter("\\Z").next(); return new Scanner(url.openStream()).useDelimiter("\\Z").next();
} }
/**
* Gets a buffered reader for reading a given file
*
* @param path <p>The path of the file to read</p>
* @return <p>A buffered reader for reading the file</p>
* @throws FileNotFoundException <p>If the file does not exist</p>
*/
public static BufferedReader getFileReader(String path) throws FileNotFoundException {
return new BufferedReader(new InputStreamReader(new FileInputStream(path)));
}
/**
* 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>
*/
public static BufferedWriter getFileWriter(String path) throws FileNotFoundException {
return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path)));
}
/**
* Reads a file from disk
*
* @param path <p>The path of the file to read</p>
* @return <p>The contents of the file</p>
* @throws IOException <p>If unable to find or read the file</p>
*/
public static String readFile(String path) throws IOException {
return CommonFunctions.readBufferedReader(getFileReader(path));
}
/**
* Writes text to a file and adds a trailing newline
*
* @param path <p>The path of the file to write to</p>
* @param text <p>The text to write</p>
* @throws IOException <p>If unable to write to the file</p>
*/
public static void writeFile(String path, String text) throws IOException {
writeFile(path, text, !text.equals(""));
}
/**
* Writes text to a file
*
* @param path <p>The path of the file to write to</p>
* @param text <p>The text to write</p>
* @param addTrailingNewline <p>Whether to add a new line at the end of the file</p>
* @throws IOException <p>If unable to write to the file</p>
*/
public static void writeFile(String path, String text, Boolean addTrailingNewline) throws IOException {
BufferedWriter writer = getFileWriter(path);
writer.write(text);
if (addTrailingNewline) {
writer.newLine();
}
writer.close();
}
/**
* Appends text to a file
*
* @param path <p>The path to the file to append to</p>
* @param text <p>The text to append</p>
* @throws IOException <p>If unable to append to the file</p>
*/
public static void appendFile(String path, String text) throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(path, true));
writer.write(text);
writer.newLine();
writer.close();
}
/** /**
* Downloads a file from a website and replaces the target file * Downloads a file from a website and replaces the target file
* *
@ -124,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>
*/
public 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. * Opens an url in the user's default application.
* *
@ -195,42 +229,20 @@ public final class CommonFunctions {
* @throws IOException <p>If unable to read from the buffered reader</p> * @throws IOException <p>If unable to read from the buffered reader</p>
*/ */
public static String readBufferedReader(BufferedReader reader) throws IOException { public static String readBufferedReader(BufferedReader reader) throws IOException {
String line; //String line;
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
while (reader.ready() && (line = reader.readLine()) != null) { char[] readCharacters = new char[1000];
text.append(line).append("\n"); while (reader.ready()) {
if (reader.read(readCharacters) > 0) {
text.append(readCharacters);
readCharacters = new char[1000];
} else {
return text.toString().trim();
}
} }
return text.toString().trim(); 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 * Validates that a name is not empty and does not contain invalid characters
@ -239,6 +251,34 @@ public final class CommonFunctions {
* @return <p>True if the name is valid</p> * @return <p>True if the name is valid</p>
*/ */
public static boolean nameIsValid(String name) { public static boolean nameIsValid(String name) {
return !name.equals("") && name.matches("[^!?;,]+"); return name != null && !name.equals("") && name.matches("[^!?;,]+");
} }
/**
* Removes all files within a folder
*
* @param target <p>The folder to delete from</p>
*/
public static void removeFilesRecursively(File target) {
File[] oldFiles = target.listFiles();
if (oldFiles == null) {
throw new IllegalArgumentException("Unable to list files in directory");
}
for (File file : oldFiles) {
if (file.isFile()) {
if (!file.delete()) {
throw new IllegalArgumentException("Unable to delete a file from the directory");
}
}
}
for (File file : oldFiles) {
if (file.isDirectory()) {
removeFilesRecursively(file);
if (!file.delete()) {
throw new IllegalArgumentException("Unable to delete a file from the directory");
}
}
}
}
} }

View File

@ -60,6 +60,9 @@ public final class JarDownloader {
continue; continue;
} }
for (String version : type.getVersions()) { for (String version : type.getVersions()) {
if ((type.getName().equals("Spigot") || type.getName().equals("Bukkit")) && version.equals("Latest")) {
continue;
}
gui.setStatus("Downloading " + type.getName() + version + "..."); gui.setStatus("Downloading " + type.getName() + version + "...");
boolean success = type.downloadJar(jarDirectory, version); boolean success = type.downloadJar(jarDirectory, version);
if (!success) { if (!success) {

View File

@ -5,16 +5,18 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.userinterface.GUI; import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
import javax.swing.*; import javax.swing.*;
import java.io.*; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Scanner; import java.util.Scanner;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.downloadFile;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsScanner; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.getResourceAsScanner;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readFile; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.readRemoteFile;
/** /**
* A utility used for updating the software * A utility used for updating the software
@ -27,13 +29,12 @@ public final class Updater {
private static final String targetFile = "minecraft-server-launcher.jar"; 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> * @return <p>An array of Channel, Version</p>
* @param updateChannel <p>The release channel to use</p> * @throws FileNotFoundException <p>If unable to find or read currentversion.csv</p>
* @throws IOException <p>If the update data cannot be read</p>
*/ */
public static void checkForUpdate(String updateURL, String updateChannel) throws IOException { public static String[] getCurrentVersion() throws FileNotFoundException {
Scanner file = getResourceAsScanner("currentversion.csv"); Scanner file = getResourceAsScanner("currentversion.csv");
if (!file.hasNextLine()) { if (!file.hasNextLine()) {
throw new FileNotFoundException("File currentversion.csv is invalid"); throw new FileNotFoundException("File currentversion.csv is invalid");
@ -44,12 +45,26 @@ public final class Updater {
} }
String oldVer = file.nextLine(); String oldVer = file.nextLine();
file.close(); file.close();
return new String[]{oldType, oldVer};
}
String data = readFile(updateURL); /**
* 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(); JsonObject jsonObject = new JsonParser().parse(data).getAsJsonObject();
String latest = jsonObject.getAsJsonObject("latest").get(updateChannel).getAsString(); String latest = jsonObject.getAsJsonObject("latest").get(updateChannel).getAsString();
if (!oldType.equals(updateChannel) || !oldVer.equals(latest)) { if (!oldType.equals(updateChannel) || isVersionHigher(oldVer, latest)) {
JsonArray versionList = jsonObject.getAsJsonArray("versions"); JsonArray versionList = jsonObject.getAsJsonArray("versions");
String url = ""; String url = "";
for (JsonElement elem : versionList) { for (JsonElement elem : versionList) {
@ -75,6 +90,27 @@ public final class Updater {
} }
} }
/**
* Decides whether one version number is higher than another
*
* @param oldVersion <p>The old version to check</p>
* @param newVersion <p>The new version to check</p>
* @return <p>True if the new version is higher than the old one</p>
*/
public static boolean isVersionHigher(String oldVersion, String newVersion) {
String[] oldVersionParts = oldVersion.split("\\.");
String[] newVersionParts = newVersion.split("\\.");
int versionLength = Math.max(oldVersionParts.length, newVersionParts.length);
for (int i = 0; i < versionLength; i++) {
int oldVersionNumber = oldVersionParts.length > i ? Integer.parseInt(oldVersionParts[i]) : 0;
int newVersionNumber = newVersionParts.length > i ? Integer.parseInt(newVersionParts[i]) : 0;
if (newVersionNumber != oldVersionNumber) {
return newVersionNumber > oldVersionNumber;
}
}
return false;
}
/** /**
* Updates the software * Updates the software
* *
@ -82,8 +118,9 @@ public final class Updater {
* @throws IOException <p>If unable to run the updater</p> * @throws IOException <p>If unable to run the updater</p>
*/ */
private static void runUpdater(String url) throws IOException { private static void runUpdater(String url) throws IOException {
String javaCommand = ServerLauncherController.getInstance().getJavaCommand();
ProcessBuilder builder; ProcessBuilder builder;
builder = new ProcessBuilder("java", "-jar", "Updater.jar", url, "yes", targetFile, "5", "nogui"); builder = new ProcessBuilder(javaCommand, "-jar", "Updater.jar", url, "yes", targetFile, "5", "nogui");
builder.directory(new File(Main.getApplicationWorkDirectory())); builder.directory(new File(Main.getApplicationWorkDirectory()));
builder.redirectErrorStream(true); builder.redirectErrorStream(true);
builder.start(); builder.start();

View File

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

View File

@ -5,4 +5,7 @@ downloadJarsText=This option will download all the .jar files available in the p
aboutText=This software was created to start and manage several servers simultaneously._BREAK_You no longer have to do the tedious work of manually downloading different .jar files every time you want to try something new. aboutText=This software was created to start and manage several servers simultaneously._BREAK_You no longer have to do the tedious work of manually downloading different .jar files every time you want to try something new.
infoURL=https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncherInfo/ infoURL=https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncherInfo/
manualUpdateURL=https://git.knarcraft.net/KnarCraft/Minecraft-Server-Launcher/releases manualUpdateURL=https://git.knarcraft.net/KnarCraft/Minecraft-Server-Launcher/releases
storyURL=https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncherStory/ storyURL=https://archive.knarcraft.net/Scripts/BungeeMinecraftServerLauncherStory/
javaCommandText=This option allows you to set a custom command/path to the Java executable used for Minecraft 1.17 and above._BREAK_If "java" is currently pointing to Java 8, you can use this to set a custom one for running new Minecraft servers and BuildTools.
oldJavaCommandText=This option allows you to set a custom command/path to the Java executable used for Minecraft 1.16 and below._BREAK_If "java" is currently pointing to Java 16, you can use this to set a custom one for running old Minecraft servers.
deleteBuiltJarFilesText=This option allows you to easily delete built Spigot and Bukkit .jar files._BREAK_You should occasionally run this option to update the built .jar files to ensure you're running the latest update.
Can't render this file because it contains an unexpected character in line 9 and column 136.

View File

@ -1,11 +1,11 @@
Vanilla;Latest,Snapshot,1.16.2,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;1.16.2,1.16.1,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- 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.16.1,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- #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;1.16.2,1.16.1,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- 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- #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 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- 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.16,1.15,1.14,1.13,1.12,1.11;https://papermc.io/api/v1/waterfall/;"latest":";";https://papermc.io/api/v1/waterfall/ 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/v1/travertine/;"latest":";";https://papermc.io/api/v1/travertine/ 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; Custom;
Can't render this file because it contains an unexpected character in line 1 and column 193.

View File

@ -0,0 +1,106 @@
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;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import javax.naming.ConfigurationException;
import java.io.File;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class JarBuilderTest {
private static JarBuilder jarBuilder;
private static String targetDirectory;
private static String jarDirectory;
@BeforeAll
public static void preSetUp() {
try {
ServerLauncherController.getInstance().loadState(true);
} catch (ConfigurationException | IOException e) {
e.printStackTrace();
}
}
@BeforeEach
public void setUp() {
targetDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" + File.separator +
"BuildTools" + File.separator;
jarDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" + File.separator +
"testjars" + File.separator;
jarBuilder = new JarBuilder(targetDirectory, jarDirectory, new FakeGUI());
removeBuildToolsFiles();
}
@AfterAll
public static void tearDown() {
removeBuildToolsFiles();
}
@Test
@Order(3)
public void buildLatestSpigotJarTest() {
File spigotFile = new File(jarDirectory + "SpigotLatest.jar");
if (spigotFile.exists() && !spigotFile.delete()) {
throw new IllegalArgumentException("Unable to remove existing spigot .jar");
}
jarBuilder.buildSpigotJar();
assertTrue(spigotFile.exists());
}
@Test
@Order(4)
public void buildLatestBukkitJarTest() {
File bukkitFile = new File(jarDirectory + "BukkitLatest.jar");
if (bukkitFile.exists() && !bukkitFile.delete()) {
throw new IllegalArgumentException("Unable to remove existing bukkit .jar");
}
jarBuilder.buildBukkitJar();
assertTrue(new File(jarDirectory + "BukkitLatest.jar").exists());
}
@Test
@Order(2)
public void downloadLatestBuildToolsJarTest() {
jarBuilder.downloadBuildTools();
assertTrue(new File(targetDirectory + "BuildTools.jar").exists());
}
@Test
@Order(1)
public void getLatestBuildToolsVersionTest() {
try {
String latestVersion = jarBuilder.getLatestBuildToolsVersion();
assertNotEquals("", latestVersion);
int newVersion = Integer.parseInt(latestVersion);
assertNotEquals(newVersion, 0);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Removes build tools files
*/
private static void removeBuildToolsFiles() {
File target = new File(targetDirectory);
if (!target.exists() && !target.mkdirs()) {
throw new IllegalArgumentException("Unable to create the test files directory");
}
CommonFunctions.removeFilesRecursively(target);
}
}

View File

@ -1,17 +1,17 @@
package net.knarcraft.minecraftserverlauncher.server; package net.knarcraft.minecraftserverlauncher.server;
import org.junit.Test; import org.junit.jupiter.api.Test;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import static junit.framework.TestCase.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static junit.framework.TestCase.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
public class ServerTest { public class ServerTest {
@Test @Test
public void fromStringTest() throws ConfigurationException { 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("asd", server.getName());
assertEquals("/home/", server.getPath()); assertEquals("/home/", server.getPath());
assertFalse(server.isEnabled()); assertFalse(server.isEnabled());

View File

@ -1,13 +1,12 @@
package net.knarcraft.minecraftserverlauncher.server; package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType; import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
import org.junit.Test; import org.junit.jupiter.api.Test;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import java.util.Arrays; import java.util.Arrays;
import static junit.framework.TestCase.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class ServerTypeHandlerTest { public class ServerTypeHandlerTest {

View File

@ -2,13 +2,16 @@ package net.knarcraft.minecraftserverlauncher.server;
import net.knarcraft.minecraftserverlauncher.Main; import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions; import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import java.io.*; import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import static junit.framework.TestCase.assertEquals;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.createAllFolders; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.createAllFolders;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ServerVersionContainerTest { public class ServerVersionContainerTest {
@ -16,7 +19,7 @@ public class ServerVersionContainerTest {
private final String versionFile = filesDirectory + File.separator + "versions.csv"; private final String versionFile = filesDirectory + File.separator + "versions.csv";
private ServerVersionContainer serverVersionContainer; private ServerVersionContainer serverVersionContainer;
@Before @BeforeEach
public void setUp() { public void setUp() {
try { try {
createAllFolders(); createAllFolders();
@ -35,15 +38,16 @@ public class ServerVersionContainerTest {
serverVersionContainer.reset(); serverVersionContainer.reset();
System.out.println(serverVersionContainer.toString()); System.out.println(serverVersionContainer.toString());
assertEquals("vanillaVersion;null\nsnapshotVersion;null\nbungeeVersion;null\nwaterfallVersions;\n" + assertEquals("vanillaVersion;null\nsnapshotVersion;null\nbungeeVersion;null\nwaterfallVersions;\n" +
"travertineVersions;\nspongeVanillaVersions;\nspongeForgeVersions;", "travertineVersions;\npaperVersions;\nspongeVanillaVersions;\nspongeForgeVersions;\ndownloadedBuildToolsVersion;null",
serverVersionContainer.toString()); serverVersionContainer.toString());
} }
@Test @Test
public void saveStateTest() throws IOException { public void saveStateTest() throws IOException {
serverVersionContainer.saveState(); serverVersionContainer.saveState();
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(versionFile))); BufferedReader reader = CommonFunctions.getFileReader(versionFile);
String savedData = CommonFunctions.readBufferedReader(reader); String savedData = CommonFunctions.readBufferedReader(reader);
reader.close();
assertEquals(serverVersionContainer.toString(), savedData); assertEquals(serverVersionContainer.toString(), savedData);
} }

View File

@ -1,19 +1,31 @@
package net.knarcraft.minecraftserverlauncher.userinterface; package net.knarcraft.minecraftserverlauncher.userinterface;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class FakeGUI extends MessageHandler implements GUI { public class FakeGUI extends MessageHandler implements GUI {
private final BufferedWriter writer;
/*** /***
* Initializes a new fake gui * Initializes a new fake gui
*/ */
public FakeGUI() { public FakeGUI() {
super(true); super(true);
this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
} }
@Override @Override
public void setStatus(String message) { public void setStatus(String message) {
System.out.println(message); try {
writer.write(message);
writer.newLine();
writer.flush();
} catch (IOException e) {
System.out.println(message);
}
} }
@Override @Override

View File

@ -1,19 +1,12 @@
package net.knarcraft.minecraftserverlauncher.utility; package net.knarcraft.minecraftserverlauncher.utility;
import net.knarcraft.minecraftserverlauncher.Main; import org.junit.jupiter.api.Test;
import org.junit.Test;
import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween; import static net.knarcraft.minecraftserverlauncher.utility.CommonFunctions.stringBetween;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class CommonFunctionsTest { public class CommonFunctionsTest {
@Test
public void saveProfileTest() {
Main.getController().addProfile("Test");
Main.getController().saveState();
}
@Test @Test
public void stringBetweenTest() { public void stringBetweenTest() {
String substring = stringBetween("fish'nchips", "f", "'"); String substring = stringBetween("fish'nchips", "f", "'");

View File

@ -4,28 +4,35 @@ import net.knarcraft.minecraftserverlauncher.Main;
import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler; import net.knarcraft.minecraftserverlauncher.server.ServerTypeHandler;
import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType; import net.knarcraft.minecraftserverlauncher.server.servertypes.ServerType;
import net.knarcraft.minecraftserverlauncher.userinterface.FakeGUI; import net.knarcraft.minecraftserverlauncher.userinterface.FakeGUI;
import org.junit.AfterClass; import org.junit.jupiter.api.AfterAll;
import org.junit.BeforeClass; import org.junit.jupiter.api.BeforeAll;
import org.junit.Test; import org.junit.jupiter.api.Test;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import static junit.framework.TestCase.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static junit.framework.TestCase.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class JarDownloaderTest { public class JarDownloaderTest {
private static String targetDirectory; private static String targetDirectory;
@BeforeClass @BeforeAll
public static void setUp() { public static void setUp() {
targetDirectory = Main.getApplicationWorkDirectory() + File.separator + "files" + File.separator + String filesDirectory = Main.getApplicationWorkDirectory() + File.separator + "files";
"testjars" + File.separator; try {
CommonFunctions.createFolder(new File(filesDirectory + File.separator + "testjars"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
targetDirectory = filesDirectory + File.separator + "testjars" + File.separator;
removeDownloadedFiles(); removeDownloadedFiles();
} }
@AfterClass @AfterAll
public static void cleanUp() { public static void cleanUp() {
removeDownloadedFiles(); removeDownloadedFiles();
} }
@ -36,7 +43,7 @@ public class JarDownloaderTest {
downloader.downloadJars(); downloader.downloadJars();
} }
@Test /*@Test
public void spongeVanillaDownloadTest() throws IOException, ConfigurationException { public void spongeVanillaDownloadTest() throws IOException, ConfigurationException {
singleDownloadTest(ServerTypeHandler.getByName("SpongeVanilla")); singleDownloadTest(ServerTypeHandler.getByName("SpongeVanilla"));
} }
@ -44,7 +51,7 @@ public class JarDownloaderTest {
@Test @Test
public void spongeForgeDownloadTest() throws IOException, ConfigurationException { public void spongeForgeDownloadTest() throws IOException, ConfigurationException {
singleDownloadTest(ServerTypeHandler.getByName("SpongeForge")); singleDownloadTest(ServerTypeHandler.getByName("SpongeForge"));
} }*/
@Test @Test
public void spigotDownloadTest() throws ConfigurationException, IOException { public void spigotDownloadTest() throws ConfigurationException, IOException {
@ -57,7 +64,7 @@ public class JarDownloaderTest {
} }
@Test @Test
public void craftbukkitDownloadTest() throws ConfigurationException, IOException { public void craftBukkitDownloadTest() throws ConfigurationException, IOException {
singleDownloadTest(ServerTypeHandler.getByName("Bukkit")); singleDownloadTest(ServerTypeHandler.getByName("Bukkit"));
} }
@ -95,6 +102,10 @@ public class JarDownloaderTest {
private void singleDownloadTest(ServerType serverType) throws IOException { private void singleDownloadTest(ServerType serverType) throws IOException {
assertNotNull(serverType); assertNotNull(serverType);
for (String serverVersion : serverType.getVersions()) { for (String serverVersion : serverType.getVersions()) {
if ((serverType.getName().equals("Spigot") || serverType.getName().equals("Bukkit"))
&& serverVersion.equals("Latest")) {
continue;
}
System.out.println("Downloading " + serverType.getName() + serverVersion + ".jar"); System.out.println("Downloading " + serverType.getName() + serverVersion + ".jar");
serverType.downloadJar(targetDirectory, serverVersion); serverType.downloadJar(targetDirectory, serverVersion);
assertTrue(new File(targetDirectory + serverType.getName() + serverVersion + ".jar").exists()); assertTrue(new File(targetDirectory + serverType.getName() + serverVersion + ".jar").exists());
@ -109,12 +120,6 @@ public class JarDownloaderTest {
if (!target.exists() && !target.mkdirs()) { if (!target.exists() && !target.mkdirs()) {
throw new IllegalArgumentException("Unable to create the test files directory"); throw new IllegalArgumentException("Unable to create the test files directory");
} }
File[] oldFiles = target.listFiles(); CommonFunctions.removeFilesRecursively(target);
if (oldFiles == null) {
throw new IllegalArgumentException("Unable to list files in jar test directory");
}
for (File file : oldFiles) {
assertTrue(file.delete());
}
} }
} }

View File

@ -0,0 +1,23 @@
package net.knarcraft.minecraftserverlauncher.utility;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class UpdaterTest {
@Test
public void isVersionHigherTest() {
assertTrue(Updater.isVersionHigher("1.1.1", "1.1.2"));
assertFalse(Updater.isVersionHigher("1.1.1", "1.1.1"));
assertFalse(Updater.isVersionHigher("1.1.1", "1.1.0"));
assertTrue(Updater.isVersionHigher("1.1.1", "1.2.1"));
assertFalse(Updater.isVersionHigher("1.2.1", "1.1.6"));
assertTrue(Updater.isVersionHigher("1.2.1", "2.1.6"));
assertTrue(Updater.isVersionHigher("1.2.1", "2.0.0"));
assertTrue(Updater.isVersionHigher("1.2", "1.2.1"));
assertTrue(Updater.isVersionHigher("1.1.2", "1.2"));
}
}