Adds a proper GUI to display backup progress
All checks were successful
KnarCraft/Minecraft-Server-Launcher/pipeline/head This commit looks good
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
This commit is contained in:
parent
bf77c13072
commit
60fdcf5ddc
@ -11,6 +11,7 @@ import javax.naming.ConfigurationException;
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Keeps track of a set of servers and some user settings
|
||||
@ -22,7 +23,7 @@ import java.util.ArrayList;
|
||||
public class Profile {
|
||||
|
||||
private static final ServerLauncherGUI serverLauncherGui = Main.getController().getGUI();
|
||||
private final ArrayList<Collection> collections;
|
||||
private final List<Collection> collections;
|
||||
private final String name;
|
||||
private boolean runInBackground;
|
||||
private int delayStartup;
|
||||
@ -106,7 +107,7 @@ public class Profile {
|
||||
*
|
||||
* @return <p>All collections stored by this profile</p>
|
||||
*/
|
||||
public ArrayList<Collection> getCollections() {
|
||||
public List<Collection> getCollections() {
|
||||
return this.collections;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
package net.knarcraft.minecraftserverlauncher.userinterface;
|
||||
|
||||
import net.knarcraft.minecraftserverlauncher.utility.BackupUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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 || progressBar == null) {
|
||||
return;
|
||||
}
|
||||
progressTextArea.setText(infoText);
|
||||
progressBar.setValue(progressPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the backup GUI
|
||||
*/
|
||||
public static void destroy() {
|
||||
BackupUtil.abortBackup();
|
||||
progressTextArea = null;
|
||||
progressBar = null;
|
||||
cancelButton = null;
|
||||
if (frame != null) {
|
||||
frame.dispose();
|
||||
}
|
||||
frame = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if (actionEvent.getSource() == cancelButton) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import net.knarcraft.minecraftserverlauncher.Main;
|
||||
import net.knarcraft.minecraftserverlauncher.profile.Collection;
|
||||
import net.knarcraft.minecraftserverlauncher.profile.ServerLauncherController;
|
||||
import net.knarcraft.minecraftserverlauncher.server.ServerHandler;
|
||||
import net.knarcraft.minecraftserverlauncher.utility.CommonFunctions;
|
||||
import net.knarcraft.minecraftserverlauncher.utility.BackupUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.naming.ConfigurationException;
|
||||
@ -334,7 +334,7 @@ public class ServerLauncherGUI extends MessageHandler implements ActionListener,
|
||||
addServer();
|
||||
} else if (actionSource == backupButton) {
|
||||
//Run backup in its own thread to prevent locking up
|
||||
Executors.newSingleThreadExecutor().execute(() -> CommonFunctions.backup(this));
|
||||
Executors.newSingleThreadExecutor().execute(() -> BackupUtil.backup(this));
|
||||
} else if (actionSource == addProfileButton) {
|
||||
controller.addProfile(JOptionPane.showInputDialog("Profile name: "));
|
||||
updateProfiles();
|
||||
|
@ -0,0 +1,223 @@
|
||||
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.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A helper class for performing server backup
|
||||
*/
|
||||
public class BackupUtil {
|
||||
|
||||
private static boolean backupAborted;
|
||||
private static boolean backupRunning = false;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* @throws IOException <p>If we can't start a file stream</p>
|
||||
*/
|
||||
private static synchronized 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
|
||||
*/
|
||||
public synchronized 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);
|
||||
|
||||
long backupFileSize = getFolderSize(gui, serverFolders);
|
||||
|
||||
gui.setStatus("Backing up " + (backupFileSize / 1000000) + "MB");
|
||||
|
||||
new BackupGUI();
|
||||
BackupGUI.updateProgress("Backup starting...", 0);
|
||||
long alreadyCopied = 0;
|
||||
for (List<File> serverFolder : serverFolders) {
|
||||
if (backupAborted) {
|
||||
gui.setStatus("Backup aborted");
|
||||
backupRunning = false;
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
gui.setStatus("Backup caused an error");
|
||||
backupRunning = false;
|
||||
}
|
||||
}
|
||||
backupRunning = false;
|
||||
BackupGUI.destroy();
|
||||
if (backupAborted) {
|
||||
gui.setStatus("Backup aborted");
|
||||
} else {
|
||||
gui.setStatus("Backup finished");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package net.knarcraft.minecraftserverlauncher.utility;
|
||||
|
||||
import net.knarcraft.minecraftserverlauncher.Main;
|
||||
import net.knarcraft.minecraftserverlauncher.profile.Collection;
|
||||
import net.knarcraft.minecraftserverlauncher.server.Server;
|
||||
import net.knarcraft.minecraftserverlauncher.userinterface.GUI;
|
||||
import net.knarcraft.minecraftserverlauncher.userinterface.WebBrowser;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@ -16,7 +13,6 @@ import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
@ -36,7 +32,6 @@ import java.util.Scanner;
|
||||
public final class CommonFunctions {
|
||||
|
||||
private static final String filesDirectory = Main.getApplicationWorkDirectory() + File.separator + "files";
|
||||
private static boolean backupRunning = false;
|
||||
|
||||
/**
|
||||
* Creates all folders necessary for tests and normal operation
|
||||
@ -211,55 +206,6 @@ public final class CommonFunctions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copies a folder to another location
|
||||
*
|
||||
* @param source <p>The folder to copy</p>
|
||||
* @param destination <p>Target destination</p>
|
||||
* @throws IOException <p>If we can't start a file stream</p>
|
||||
*/
|
||||
private static void copyFolder(GUI serverLauncherGui, File source, File destination) throws IOException {
|
||||
if (!source.isDirectory()) {
|
||||
copyFile(serverLauncherGui, source, destination);
|
||||
} else {
|
||||
serverLauncherGui.setStatus("Copying directory " + source);
|
||||
if (!destination.exists() && !destination.mkdir()) {
|
||||
return;
|
||||
}
|
||||
String[] files = source.list();
|
||||
if (files != null) {
|
||||
for (String file : files) {
|
||||
File srcFile = new File(source, file);
|
||||
File destinationFile = new File(destination, file);
|
||||
copyFolder(serverLauncherGui, srcFile, destinationFile);
|
||||
}
|
||||
}
|
||||
serverLauncherGui.setStatus("Copied directory " + source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file from one location to another
|
||||
*
|
||||
* @param serverLauncherGui <p>The serverLauncherGui to use for alerting the user</p>
|
||||
* @param source <p>The file to copy</p>
|
||||
* @param destination <p>The location of the copied file</p>
|
||||
* @throws IOException <p>If reading or writing fails</p>
|
||||
*/
|
||||
private static void copyFile(GUI serverLauncherGui, File source, File destination) throws IOException {
|
||||
serverLauncherGui.setStatus("Copying file " + source + "...");
|
||||
InputStream in = new FileInputStream(source);
|
||||
OutputStream out = new FileOutputStream(destination);
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = in.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, length);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
serverLauncherGui.setStatus("Copied file " + source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an url in the user's default application.
|
||||
*
|
||||
@ -296,50 +242,7 @@ public final class CommonFunctions {
|
||||
return text.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all server directories to a folder specified by the user.
|
||||
*/
|
||||
public synchronized static void backup(GUI gui) {
|
||||
if (backupRunning) {
|
||||
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...");
|
||||
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()) {
|
||||
backupRunning = false;
|
||||
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();
|
||||
backupRunning = false;
|
||||
}
|
||||
}
|
||||
gui.setStatus("Backup finished");
|
||||
backupRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a name is not empty and does not contain invalid characters
|
||||
|
Loading…
Reference in New Issue
Block a user