summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommands.java70
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java6
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java25
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/BackupNameTimeFormatter.java22
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java11
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java12
7 files changed, 117 insertions, 31 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
index 4b4dd59..79029bf 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
@@ -33,5 +33,7 @@ public final class KBCommandRegister {
// register /kb cancel for cancelling the execution to be confirmed. OP is required.
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("cancel").requires(PermissionValidator::op).executes(KBCommands::cancel)));
+ // register /kb prev for showing the latest backup.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("prev").requires(PermissionValidator::op).executes(KBCommands::prev)));
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
index be4d151..5302150 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
@@ -2,6 +2,8 @@ package com.keuin.kbackupfabric;
import com.keuin.kbackupfabric.data.BackupMetadata;
import com.keuin.kbackupfabric.data.PendingOperation;
+import com.keuin.kbackupfabric.util.BackupFilesystemUtil;
+import com.keuin.kbackupfabric.util.BackupNameTimeFormatter;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.worker.BackupWorker;
import com.keuin.kbackupfabric.worker.RestoreWorker;
@@ -14,9 +16,10 @@ import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
import static com.keuin.kbackupfabric.util.PrintUtil.*;
@@ -30,7 +33,7 @@ public final class KBCommands {
private static final Logger LOGGER = LogManager.getLogger();
- private static final HashMap<Integer, String> backupIndexNameMapper = new HashMap<>(); // index -> backupName
+ private static final List<String> backupNameList = new ArrayList<>(); // index -> backupName
private static PendingOperation pendingOperation = null;
/**
@@ -56,13 +59,13 @@ public final class KBCommands {
File[] files = getBackupSaveDirectory(server).listFiles(
(dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix())
);
- backupIndexNameMapper.clear();
+ backupNameList.clear();
if (files != null) {
int i = 0;
for (File file : files) {
++i;
String backupName = getBackupName(file.getName());
- backupIndexNameMapper.put(i, backupName);
+ backupNameList.add(backupName);
msgInfo(context, String.format("[%d] %s, size: %.1fMB", i, backupName, file.length() * 1.0 / 1024 / 1024));
}
}
@@ -160,9 +163,7 @@ public final class KBCommands {
private static int doBackup(CommandContext<ServerCommandSource> context, String customName) {
// Real backup name (compatible with legacy backup): date_name, such as 2020-04-23_21-03-00_test
//KBMain.backup("name")
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
- String timeString = LocalDateTime.now().format(formatter);
- String backupName = timeString + "_" + customName;
+ String backupName = BackupNameTimeFormatter.getTimeString() + "_" + customName;
// Validate file name
final char[] ILLEGAL_CHARACTERS = {'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'};
@@ -265,14 +266,51 @@ public final class KBCommands {
}
}
- private static String parseBackupName(CommandContext<ServerCommandSource> context, String userInput) {
- MinecraftServer server = context.getSource().getMinecraftServer();
- String backupName = StringArgumentType.getString(context, "backupName");
+ /**
+ * Show the most recent backup.
+ * If there is no available backup, print specific info.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int prev(CommandContext<ServerCommandSource> context) {
+ try {
+ // List all backups
+ MinecraftServer server = context.getSource().getMinecraftServer();
+ List<File> files = Arrays.asList(Objects.requireNonNull(getBackupSaveDirectory(server).listFiles()));
+ files.removeIf(f -> !f.getName().startsWith(BackupFilesystemUtil.getBackupFileNamePrefix()));
+ files.sort((x, y) -> (int) (BackupFilesystemUtil.getBackupTimeFromBackupFileName(y.getName()) - BackupFilesystemUtil.getBackupTimeFromBackupFileName(x.getName())));
+ File prevBackupFile = files.get(0);
+ String backupName = getBackupName(prevBackupFile.getName());
+ int i = backupNameList.indexOf(backupName);
+ if (i == -1) {
+ backupNameList.add(backupName);
+ i = backupNameList.size();
+ } else {
+ ++i;
+ }
+ msgInfo(context, String.format("The most recent backup: [%d] %s , size: %s", i, backupName, humanFileSize(prevBackupFile.length())));
+ } catch (NullPointerException e) {
+ msgInfo(context, "There are no backups available.");
+ } catch (SecurityException ignored) {
+ msgErr(context, "Failed to read file.");
+ return FAILED;
+ }
+ return SUCCESS;
+ }
- if (backupName.matches("[0-9]*")) {
- // If numeric input
- Integer index = Integer.parseInt(backupName);
- return backupIndexNameMapper.get(index); // Replace input number with real backup name.
+
+ private static String parseBackupName(CommandContext<ServerCommandSource> context, String userInput) {
+ try {
+ MinecraftServer server = context.getSource().getMinecraftServer();
+ String backupName = StringArgumentType.getString(context, "backupName");
+
+ if (backupName.matches("[0-9]*")) {
+ // If numeric input
+ int index = Integer.parseInt(backupName) - 1;
+ return backupNameList.get(index); // Replace input number with real backup name.
+ }
+ } catch (NumberFormatException | IndexOutOfBoundsException ignored) {
}
return userInput;
}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
index 3b1901e..47983b7 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
@@ -29,14 +29,14 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback
@Override
public void onInitialize() {
- System.out.println("KBackup: Binding events and commands ...");
+ System.out.println("[KBackup] Binding events and commands ...");
CommandRegistry.INSTANCE.register(false, KBCommandRegister::registerCommands);
ServerStartCallback.EVENT.register(this);
}
@Override
public void onStartServer(MinecraftServer server) {
- LOGGER.debug("KBackup: Initializing ...");
+ LOGGER.debug("[KBackup] Initializing ...");
// Update backup suggestion list
BackupNameSuggestionProvider.setBackupSaveDirectory(BackupFilesystemUtil.getBackupSaveDirectory(server).getPath());
@@ -55,7 +55,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback
fileInputStream.close();
// Print metadata
- LOGGER.info("Recovered from previous backup:");
+ LOGGER.info("[KBackup] Recovered from previous backup:");
LOGGER.info("Backup Name: " + metadata.getBackupName());
LOGGER.info("Create Time: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(metadata.getBackupTime())));
diff --git a/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java
index 5b8ba5a..ce39615 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java
@@ -5,6 +5,8 @@ import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.world.World;
import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Functions deal with file name, directory name about Minecraft saves.
@@ -50,4 +52,27 @@ public final class BackupFilesystemUtil {
public static String getBackupFileNamePrefix() {
return backupFileNamePrefix;
}
+
+ public static long getBackupTimeFromBackupFileName(String backupFileName) {
+ Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(backupFileName);
+ if (matcher.find()) {
+ String timeString = matcher.group(0);
+ long timeStamp = BackupNameTimeFormatter.timeStringToEpochSeconds(timeString);
+ System.out.println(backupFileName + " -> " + timeStamp);
+ return timeStamp;
+ } else {
+ System.err.println("Failed to extract time from " + backupFileName);
+ }
+ return -1;
+ }
+
+ public static String humanFileSize(long size) {
+ double fileSize = size * 1.0 / 1024 / 1024; // Default unit is MB
+ if (fileSize > 1000)
+ //msgInfo(context, String.format("File size: %.2fGB", fileSize / 1024));
+ return String.format("%.2fGB", fileSize / 1024);
+ else
+ //msgInfo(context, String.format("File size: %.2fMB", fileSize));
+ return String.format("%.2fMB", fileSize);
+ }
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/BackupNameTimeFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/BackupNameTimeFormatter.java
new file mode 100644
index 0000000..5d00270
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/BackupNameTimeFormatter.java
@@ -0,0 +1,22 @@
+package com.keuin.kbackupfabric.util;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+public class BackupNameTimeFormatter {
+ private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
+
+ public static String getTimeString() {
+ return LocalDateTime.now().format(formatter);
+ }
+
+ public static long timeStringToEpochSeconds(String timeString) {
+ ZoneId systemZone = ZoneId.systemDefault(); // my timezone
+ LocalDateTime localDateTime = LocalDateTime.parse(timeString, formatter);
+ ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(localDateTime);
+ return localDateTime.toEpochSecond(currentOffsetForMyZone);
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
index 57e59fb..3303f70 100644
--- a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
+++ b/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
@@ -56,9 +56,9 @@ public final class BackupWorker implements Runnable {
});
// Force to save all player data and worlds
- LOGGER.debug("Saving players ...");
+ LOGGER.info("Saving players ...");
server.getPlayerManager().saveAllPlayerData();
- LOGGER.debug("Saving worlds ...");
+ LOGGER.info("Saving worlds ...");
server.save(true, true, true);
// Start threaded worker
@@ -86,6 +86,7 @@ public final class BackupWorker implements Runnable {
String levelPath = getLevelPath(server);
String backupFileName = getBackupFileName(backupName);
LOGGER.debug(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSaveDirectoryFile.toString()));
+ LOGGER.info("Compressing level ...");
ZipUtil.makeBackupZip(levelPath, backupSaveDirectoryFile.toString(), backupFileName, backupMetadata);
File backupZipFile = new File(backupSaveDirectoryFile, backupFileName);
@@ -96,11 +97,7 @@ public final class BackupWorker implements Runnable {
long timeEscapedMillis = System.currentTimeMillis() - startTime;
msgInfo(context, String.format("Backup finished. (%.2fs)", timeEscapedMillis / 1000.0), true);
try {
- double fileSize = backupZipFile.length() * 1.0 / 1024 / 1024;
- if (fileSize > 1000)
- msgInfo(context, String.format("File size: %.2fGB", fileSize / 1024));
- else
- msgInfo(context, String.format("File size: %.2fMB", fileSize));
+ msgInfo(context, String.format("File size: %s", humanFileSize(backupZipFile.length())));
} catch (SecurityException ignored) {
}
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
index 8d367d7..54e7beb 100644
--- a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
+++ b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
@@ -44,7 +44,7 @@ public final class RestoreWorker implements Runnable {
public void run() {
try {
// Wait server thread die
- LOGGER.debug("Waiting for the server thread to exit ...");
+ LOGGER.info("Waiting for the server thread to exit ...");
while (serverThread.isAlive()) {
try {
serverThread.join();
@@ -52,15 +52,16 @@ public final class RestoreWorker implements Runnable {
}
}
- LOGGER.debug("Wait for 5 seconds ...");
+ LOGGER.info("Wait for 5 seconds ...");
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) {
}
// Delete old level
- LOGGER.debug("Server stopped. Deleting old level ...");
+ LOGGER.info("Server stopped. Deleting old level ...");
File levelDirFile = new File(levelDirectory);
+ long startTime = System.currentTimeMillis();
int failedCounter = 0;
final int MAX_RETRY_TIMES = 20;
@@ -84,9 +85,10 @@ public final class RestoreWorker implements Runnable {
}
// Decompress archive
- LOGGER.debug("Decompressing archived level");
+ LOGGER.info("Decompressing archived level");
ZipUtil.unzip(backupFilePath, levelDirectory, false);
- LOGGER.info("Restore complete! Please restart the server manually.");
+ long endTime = System.currentTimeMillis();
+ LOGGER.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0));
} catch (SecurityException | IOException | ZipUtilException e) {
LOGGER.error("An exception occurred while restoring: " + e.getMessage());
}