1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
package com.keuin.kbackupfabric;
import com.keuin.kbackupfabric.data.BackupMetadata;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.worker.BackupWorker;
import com.keuin.kbackupfabric.worker.RestoreWorker;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
import static com.keuin.kbackupfabric.util.PrintUtil.*;
public final class KBCommands {
private static final int SUCCESS = 1;
private static final int FAILED = -1;
private static final HashMap<Integer, String> backupIndexNameMapper = new HashMap<>(); // index -> backupName
private static String restoreBackupNameToBeConfirmed = null;
/**
* Print the help menu.
*
* @param context the context.
* @return stat code.
*/
public static int help(CommandContext<ServerCommandSource> context) {
msgInfo(context, "==== KBackup Manual ====");
msgInfo(context, "/kb /kb help Print help menu.");
msgInfo(context, "/kb list Show all backups.");
msgInfo(context, "/kb backup [backup_name] Backup the whole level to backup_name. The default name is current system time.");
msgInfo(context, "/kb restore <backup_name> Delete the whole current level and restore from given backup. /kb restore is identical with /kb list.");
msgInfo(context, "/kb confirm Confirm and start restoring.");
msgInfo(context, "/kb cancel Cancel the restoration to be confirmed. If cancelled, /kb confirm will not run.");
return SUCCESS;
}
public static int list(CommandContext<ServerCommandSource> context) {
msgInfo(context, "Available backups: (file is not checked, manipulation may affect this plugin)");
MinecraftServer server = context.getSource().getMinecraftServer();
File[] files = getBackupSaveDirectory(server).listFiles(
(dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix())
);
backupIndexNameMapper.clear();
if (files != null) {
int i = 0;
for (File file : files) {
++i;
String backupName = getBackupName(file.getName());
backupIndexNameMapper.put(i, backupName);
msgInfo(context, String.format("[%d] %s, size: %.1fMB", i, backupName, file.length() * 1.0 / 1024 / 1024));
}
}
return SUCCESS;
}
/**
* Backup with context parameter backupName.
*
* @param context the context.
* @return stat code.
*/
public static int backup(CommandContext<ServerCommandSource> context) {
//KBMain.backup("name")
String backupName = StringArgumentType.getString(context, "backupName");
if (backupName.matches("[0-9]*")) {
// Numeric param is not allowed
backupName = String.format("a%s", backupName);
msgWarn(context, String.format("Pure numeric name is not allowed. Renamed to %s", backupName));
}
return doBackup(context, backupName);
}
/**
* Restore with context parameter backupName.
* Simply set the pending backupName to given backupName, for the second confirmation.
*
* @param context the context.
* @return stat code.
*/
public static int restore(CommandContext<ServerCommandSource> context) {
//KBMain.restore("name")
MinecraftServer server = context.getSource().getMinecraftServer();
String backupName = StringArgumentType.getString(context, "backupName");
if (backupName.matches("[0-9]*")) {
// If numeric input
Integer index = Integer.parseInt(backupName);
String realBackupName = backupIndexNameMapper.get(index);
if (realBackupName == null) {
return list(context); // Show the list and return
}
backupName = realBackupName; // Replace input number with real backup name.
}
// Validate backupName
if (!isBackupNameValid(backupName, server)) {
// Invalid backupName
msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid.", false);
return FAILED;
}
// Update confirm pending variable
restoreBackupNameToBeConfirmed = backupName;
msgWarn(context, String.format("WARNING: You will LOSE YOUR CURRENT WORLD PERMANENTLY! The worlds will be replaced with backup %s . Use /kb confirm to start or /kb cancel to abort.", restoreBackupNameToBeConfirmed), true);
return SUCCESS;
}
/**
* Backup with default name.
*
* @param context the context.
* @return stat code.
*/
public static int backupWithDefaultName(CommandContext<ServerCommandSource> context) {
//KBMain.backup("name")
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
String timeString = LocalDateTime.now().format(formatter);
return doBackup(context, timeString);
}
private static int doBackup(CommandContext<ServerCommandSource> context, String backupName) {
BackupMetadata metadata = new BackupMetadata(System.currentTimeMillis(), backupName);
BackupWorker.invoke(context, backupName, metadata);
return SUCCESS;
}
/**
* Restore with context parameter backupName.
*
* @param context the context.
* @return stat code.
*/
public static int confirm(CommandContext<ServerCommandSource> context) {
if (restoreBackupNameToBeConfirmed == null) {
msgInfo(context, "Nothing to be confirmed. Please execute /kb restore <backup_name> first.");
return FAILED;
}
// do restore to backupName
String backupName = restoreBackupNameToBeConfirmed;
PrintUtil.msgInfo(context, String.format("Restoring worlds to %s ...", backupName), true);
// Get server
MinecraftServer server = context.getSource().getMinecraftServer();
String backupFileName = getBackupFileName(backupName);
debug("Backup file name: " + backupFileName);
File backupFile = new File(getBackupSaveDirectory(server), backupFileName);
PrintUtil.msgInfo(context, "Server will shutdown in a few seconds, depended on your world size and the disk speed, the restore progress may take seconds or minutes.", true);
PrintUtil.msgInfo(context, "Please do not force the server stop, or the level would be broken.", true);
PrintUtil.msgInfo(context, "After it shuts down, please restart the server manually.", true);
final int WAIT_SECONDS = 10;
for (int i = 0; i < WAIT_SECONDS; ++i) {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
PrintUtil.msgInfo(context, "Shutting down ...", true);
RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server));
return SUCCESS;
}
/**
* Cancel the execution to be confirmed.
*
* @param context the context.
* @return stat code.
*/
public static int cancel(CommandContext<ServerCommandSource> context) {
if (restoreBackupNameToBeConfirmed != null) {
restoreBackupNameToBeConfirmed = null;
PrintUtil.msgInfo(context, "The restoration is cancelled.", true);
return SUCCESS;
} else {
msgErr(context, "Nothing to cancel.");
return FAILED;
}
}
}
|