Should work now. Only a ton of testing left...
parent
7c7c4c7493
commit
227faef84c
|
@ -92,6 +92,12 @@ public class ConfigHandler {
|
|||
"For more info: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html\n")
|
||||
public String dateTimeFormat = "dd.MM.yyyy_HH-mm-ss";
|
||||
|
||||
@Comment("\nShould old world be backed-up?\n")
|
||||
public boolean backupOldWorlds = true;
|
||||
|
||||
@Comment("\nDelay between typing-in /backup restore and it actually starting\n")
|
||||
public int restoreDelay = 30;
|
||||
|
||||
public Optional<String> sanitize() {
|
||||
if(compressionCoreCountLimit > Runtime.getRuntime().availableProcessors())
|
||||
return Optional.of("compressionCoreCountLimit is too big! Your system only has: " + Runtime.getRuntime().availableProcessors() + " cores!");
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package net.szum123321.textile_backup;
|
||||
|
||||
public interface LivingServer {
|
||||
boolean isAlive();
|
||||
}
|
|
@ -28,9 +28,9 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
|||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.szum123321.textile_backup.commands.*;
|
||||
import net.szum123321.textile_backup.core.create.BackupScheduler;
|
||||
import net.szum123321.textile_backup.core.restore.RestoreHelper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessageFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -39,9 +39,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
public class TextileBackup implements ModInitializer {
|
||||
public static final String MOD_ID = "textile_backup";
|
||||
public static final Logger LOGGER = LogManager.getFormatterLogger("Textile Backup");
|
||||
public static final Logger LOGGER = LogManager.getLogger("Textile Backup", ParameterizedMessageFactory.INSTANCE);
|
||||
|
||||
public static ConfigHandler config;
|
||||
public static ConfigHandler CONFIG;
|
||||
|
||||
public static final BackupScheduler scheduler = new BackupScheduler();
|
||||
public static ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
@ -49,16 +49,16 @@ public class TextileBackup implements ModInitializer {
|
|||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
config = ConfigManager.loadConfig(ConfigHandler.class);
|
||||
CONFIG = ConfigManager.loadConfig(ConfigHandler.class);
|
||||
|
||||
Optional<String> errorMessage = config.sanitize();
|
||||
Optional<String> errorMessage = CONFIG.sanitize();
|
||||
|
||||
if(errorMessage.isPresent()) {
|
||||
LOGGER.fatal("TextileBackup config file has wrong settings! \n" + errorMessage.get());
|
||||
LOGGER.fatal("TextileBackup config file has wrong settings! \n {}", errorMessage.get());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if(TextileBackup.config.backupInterval > 0)
|
||||
if(TextileBackup.CONFIG.backupInterval > 0)
|
||||
ServerTickEvents.END_SERVER_TICK.register(scheduler::tick);
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(ignored -> {
|
||||
|
@ -72,11 +72,11 @@ public class TextileBackup implements ModInitializer {
|
|||
LiteralArgumentBuilder.<ServerCommandSource>literal("backup")
|
||||
.requires((ctx) -> {
|
||||
try {
|
||||
return ((config.playerWhitelist.contains(ctx.getEntityOrThrow().getEntityName()) ||
|
||||
ctx.hasPermissionLevel(config.permissionLevel)) &&
|
||||
!config.playerBlacklist.contains(ctx.getEntityOrThrow().getEntityName())) ||
|
||||
return ((CONFIG.playerWhitelist.contains(ctx.getEntityOrThrow().getEntityName()) ||
|
||||
ctx.hasPermissionLevel(CONFIG.permissionLevel)) &&
|
||||
!CONFIG.playerBlacklist.contains(ctx.getEntityOrThrow().getEntityName())) ||
|
||||
(ctx.getMinecraftServer().isSinglePlayer() &&
|
||||
config.alwaysSingleplayerAllowed);
|
||||
CONFIG.alwaysSingleplayerAllowed);
|
||||
} catch (Exception ignored) { //Command was called from server console.
|
||||
return true;
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ public class TextileBackup implements ModInitializer {
|
|||
.then(StartBackupCommand.register())
|
||||
.then(WhitelistCommand.register())
|
||||
.then(RestoreBackupCommand.register())
|
||||
.then(ListBackupsCommand.register())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class BlacklistCommand {
|
|||
|
||||
builder.append("Currently on the blacklist are: ");
|
||||
|
||||
for(String name : TextileBackup.config.playerBlacklist){
|
||||
for(String name : TextileBackup.CONFIG.playerBlacklist){
|
||||
builder.append(name);
|
||||
builder.append(", ");
|
||||
}
|
||||
|
@ -53,11 +53,11 @@ public class BlacklistCommand {
|
|||
private static int executeAdd(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(TextileBackup.config.playerBlacklist.contains(player.getEntityName())) {
|
||||
if(TextileBackup.CONFIG.playerBlacklist.contains(player.getEntityName())) {
|
||||
ctx.getSource().sendFeedback(new TranslatableText("Player: %s is already blacklisted.", player.getEntityName()), false);
|
||||
}else{
|
||||
TextileBackup.config.playerBlacklist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.config);
|
||||
TextileBackup.CONFIG.playerBlacklist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.CONFIG);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
|
@ -65,8 +65,8 @@ public class BlacklistCommand {
|
|||
builder.append(player.getEntityName());
|
||||
builder.append(" added to the blacklist");
|
||||
|
||||
if(TextileBackup.config.playerWhitelist.contains(player.getEntityName())){
|
||||
TextileBackup.config.playerWhitelist.remove(player.getEntityName());
|
||||
if(TextileBackup.CONFIG.playerWhitelist.contains(player.getEntityName())){
|
||||
TextileBackup.CONFIG.playerWhitelist.remove(player.getEntityName());
|
||||
builder.append(" and removed form the whitelist");
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,11 @@ public class BlacklistCommand {
|
|||
private static int executeRemove(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(!TextileBackup.config.playerBlacklist.contains(player.getEntityName())) {
|
||||
if(!TextileBackup.CONFIG.playerBlacklist.contains(player.getEntityName())) {
|
||||
ctx.getSource().sendFeedback(new TranslatableText("Player: %s newer was blacklisted.", player.getEntityName()), false);
|
||||
}else{
|
||||
TextileBackup.config.playerBlacklist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.config);
|
||||
TextileBackup.CONFIG.playerBlacklist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.CONFIG);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package net.szum123321.textile_backup.commands;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ListBackupsCommand {
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("list")
|
||||
.executes(ctx -> {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("Available backups: ");
|
||||
|
||||
Arrays.stream(Utilities.getBackupRootPath(Utilities.getLevelName(ctx.getSource().getMinecraftServer()))
|
||||
.listFiles())
|
||||
.forEach(file -> builder.append(file.getName()).append(", "));
|
||||
|
||||
ctx.getSource().sendFeedback(new LiteralText(builder.toString()), false);
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,23 +19,47 @@ import java.time.format.DateTimeFormatter;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class RestoreBackupCommand {
|
||||
private final static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("restore")
|
||||
.then(CommandManager.argument("file", StringArgumentType.greedyString())
|
||||
.then(CommandManager.argument("file", StringArgumentType.word())
|
||||
.suggests(new FileSuggestionProvider())
|
||||
.executes(RestoreBackupCommand::execute));
|
||||
.executes(RestoreBackupCommand::execute)
|
||||
).then(CommandManager.argument("file", StringArgumentType.word())
|
||||
.suggests(new FileSuggestionProvider())
|
||||
.then(CommandManager.argument("comment", StringArgumentType.word())
|
||||
.executes(RestoreBackupCommand::executeWithCommand)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<ServerCommandSource> ctx) {
|
||||
String arg = StringArgumentType.getString(ctx, "file");
|
||||
LocalDateTime dateTime = LocalDateTime.from(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").parse(arg));
|
||||
String file = StringArgumentType.getString(ctx, "file");
|
||||
LocalDateTime dateTime = LocalDateTime.from(dateTimeFormatter.parse(file));
|
||||
|
||||
if(ctx.getSource().getEntity() != null)
|
||||
TextileBackup.LOGGER.info("Backup restoration was initiated by: {}", ctx.getSource().getName());
|
||||
else
|
||||
TextileBackup.LOGGER.info("Backup restoration was initiated form Server Console");
|
||||
|
||||
new Thread(RestoreHelper.create(dateTime, ctx.getSource().getMinecraftServer())).start();
|
||||
RestoreHelper.create(dateTime, ctx.getSource().getMinecraftServer(), null).start();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int executeWithCommand(CommandContext<ServerCommandSource> ctx) {
|
||||
String file = StringArgumentType.getString(ctx, "file");
|
||||
String comment = StringArgumentType.getString(ctx, "comment");
|
||||
|
||||
LocalDateTime dateTime = LocalDateTime.from(dateTimeFormatter.parse(file));
|
||||
|
||||
if(ctx.getSource().getEntity() != null)
|
||||
TextileBackup.LOGGER.info("Backup restoration was initiated by: {}", ctx.getSource().getName());
|
||||
else
|
||||
TextileBackup.LOGGER.info("Backup restoration was initiated form Server Console");
|
||||
|
||||
RestoreHelper.create(dateTime, ctx.getSource().getMinecraftServer(), comment).start();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -46,7 +70,7 @@ public class RestoreBackupCommand {
|
|||
String remaining = builder.getRemaining();
|
||||
|
||||
for(RestoreHelper.RestoreableFile file : RestoreHelper.getAvailableBackups(ctx.getSource().getMinecraftServer())) {
|
||||
String formattedCreationTime = file.getCreationTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
String formattedCreationTime = file.getCreationTime().format(dateTimeFormatter);
|
||||
|
||||
if(formattedCreationTime.startsWith(remaining)) {
|
||||
if(ctx.getSource().getEntity() != null) { //was typed by player
|
||||
|
|
|
@ -40,7 +40,7 @@ public class WhitelistCommand {
|
|||
|
||||
builder.append("Currently on the whitelist are: ");
|
||||
|
||||
for(String name : TextileBackup.config.playerWhitelist){
|
||||
for(String name : TextileBackup.CONFIG.playerWhitelist){
|
||||
builder.append(name);
|
||||
builder.append(", ");
|
||||
}
|
||||
|
@ -53,11 +53,11 @@ public class WhitelistCommand {
|
|||
private static int executeAdd(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(TextileBackup.config.playerWhitelist.contains(player.getEntityName())) {
|
||||
if(TextileBackup.CONFIG.playerWhitelist.contains(player.getEntityName())) {
|
||||
ctx.getSource().sendFeedback(new TranslatableText("Player: %s is already whitelisted.", player.getEntityName()), false);
|
||||
}else{
|
||||
TextileBackup.config.playerWhitelist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.config);
|
||||
TextileBackup.CONFIG.playerWhitelist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.CONFIG);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
|
@ -65,8 +65,8 @@ public class WhitelistCommand {
|
|||
builder.append(player.getEntityName());
|
||||
builder.append(" added to the whitelist");
|
||||
|
||||
if(TextileBackup.config.playerBlacklist.contains(player.getEntityName())){
|
||||
TextileBackup.config.playerBlacklist.remove(player.getEntityName());
|
||||
if(TextileBackup.CONFIG.playerBlacklist.contains(player.getEntityName())){
|
||||
TextileBackup.CONFIG.playerBlacklist.remove(player.getEntityName());
|
||||
builder.append(" and removed form the blacklist");
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,11 @@ public class WhitelistCommand {
|
|||
private static int executeRemove(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(!TextileBackup.config.playerWhitelist.contains(player.getEntityName())) {
|
||||
if(!TextileBackup.CONFIG.playerWhitelist.contains(player.getEntityName())) {
|
||||
ctx.getSource().sendFeedback(new TranslatableText("Player: %s newer was on the whitelist.", player.getEntityName()), false);
|
||||
}else{
|
||||
TextileBackup.config.playerWhitelist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.config);
|
||||
TextileBackup.CONFIG.playerWhitelist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(TextileBackup.CONFIG);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("Player: ");
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
A simple backup mod for Fabric
|
||||
Copyright (C) 2020 Szum123321
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
@ -37,7 +55,7 @@ public class Utilities {
|
|||
}
|
||||
}
|
||||
|
||||
for(String i : TextileBackup.config.fileBlacklist) {
|
||||
for(String i : TextileBackup.CONFIG.fileBlacklist) {
|
||||
if(path.startsWith(i))
|
||||
return true;
|
||||
}
|
||||
|
@ -72,9 +90,9 @@ public class Utilities {
|
|||
}
|
||||
|
||||
public static File getBackupRootPath(String worldName) {
|
||||
File path = new File(TextileBackup.config.path).getAbsoluteFile();
|
||||
File path = new File(TextileBackup.CONFIG.path).getAbsoluteFile();
|
||||
|
||||
if (TextileBackup.config.perWorldBackup)
|
||||
if (TextileBackup.CONFIG.perWorldBackup)
|
||||
path = path.toPath().resolve(worldName).toFile();
|
||||
|
||||
if (!path.exists()) {
|
||||
|
@ -87,7 +105,7 @@ public class Utilities {
|
|||
.getInstance()
|
||||
.getGameDirectory()
|
||||
.toPath()
|
||||
.resolve(TextileBackup.config.path)
|
||||
.resolve(TextileBackup.CONFIG.path)
|
||||
.toFile();
|
||||
}
|
||||
}
|
||||
|
@ -137,8 +155,8 @@ public class Utilities {
|
|||
}
|
||||
|
||||
public static DateTimeFormatter getDateTimeFormatter(){
|
||||
if(!TextileBackup.config.dateTimeFormat.equals(""))
|
||||
return DateTimeFormatter.ofPattern(TextileBackup.config.dateTimeFormat);
|
||||
if(!TextileBackup.CONFIG.dateTimeFormat.equals(""))
|
||||
return DateTimeFormatter.ofPattern(TextileBackup.CONFIG.dateTimeFormat);
|
||||
else
|
||||
return getBackupDateTimeFormatter();
|
||||
}
|
||||
|
@ -164,7 +182,7 @@ public class Utilities {
|
|||
if(ctx != null && ctx.getEntity() != null)
|
||||
ctx.sendFeedback(new LiteralText(s), false);
|
||||
|
||||
if(TextileBackup.config.log)
|
||||
if(TextileBackup.CONFIG.log)
|
||||
TextileBackup.LOGGER.info(s);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,13 +63,13 @@ public class BackupHelper {
|
|||
AtomicInteger deletedFiles = new AtomicInteger();
|
||||
|
||||
if (root.isDirectory() && root.exists() && root.listFiles() != null) {
|
||||
if (TextileBackup.config.maxAge > 0) { // delete files older that configured
|
||||
if (TextileBackup.CONFIG.maxAge > 0) { // delete files older that configured
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Arrays.stream(root.listFiles())
|
||||
.filter(BackupHelper::isFileOk)
|
||||
.filter(f -> Utilities.getFileCreationTime(f).isPresent()) // We check if we can get file's creation date so that the next line won't throw an exception
|
||||
.filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > TextileBackup.config.maxAge)
|
||||
.filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > TextileBackup.CONFIG.maxAge)
|
||||
.forEach(f -> {
|
||||
if(f.delete()) {
|
||||
Utilities.info("Deleting: " + f.getName(), ctx);
|
||||
|
@ -80,7 +80,7 @@ public class BackupHelper {
|
|||
});
|
||||
}
|
||||
|
||||
if (TextileBackup.config.backupsToKeep > 0 && root.listFiles().length > TextileBackup.config.backupsToKeep) {
|
||||
if (TextileBackup.CONFIG.backupsToKeep > 0 && root.listFiles().length > TextileBackup.CONFIG.backupsToKeep) {
|
||||
int i = root.listFiles().length;
|
||||
|
||||
Iterator<File> it = Arrays.stream(root.listFiles())
|
||||
|
@ -89,7 +89,7 @@ public class BackupHelper {
|
|||
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime(f).get()))
|
||||
.iterator();
|
||||
|
||||
while(i > TextileBackup.config.backupsToKeep && it.hasNext()) {
|
||||
while(i > TextileBackup.CONFIG.backupsToKeep && it.hasNext()) {
|
||||
File f = it.next();
|
||||
|
||||
if(f.delete()) {
|
||||
|
@ -103,14 +103,14 @@ public class BackupHelper {
|
|||
}
|
||||
}
|
||||
|
||||
if (TextileBackup.config.maxSize > 0 && FileUtils.sizeOfDirectory(root) / 1024 > TextileBackup.config.maxSize) {
|
||||
if (TextileBackup.CONFIG.maxSize > 0 && FileUtils.sizeOfDirectory(root) / 1024 > TextileBackup.CONFIG.maxSize) {
|
||||
Iterator<File> it = Arrays.stream(root.listFiles())
|
||||
.filter(BackupHelper::isFileOk)
|
||||
.filter(f -> Utilities.getFileCreationTime(f).isPresent())
|
||||
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime(f).get()))
|
||||
.iterator();
|
||||
|
||||
while(FileUtils.sizeOfDirectory(root) / 1024 > TextileBackup.config.maxSize && it.hasNext()) {
|
||||
while(FileUtils.sizeOfDirectory(root) / 1024 > TextileBackup.CONFIG.maxSize && it.hasNext()) {
|
||||
File f = it.next();
|
||||
|
||||
if(f.delete()) {
|
||||
|
|
|
@ -17,7 +17,7 @@ public class BackupScheduler {
|
|||
public void tick(MinecraftServer server) {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
|
||||
if(TextileBackup.config.doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
||||
if(TextileBackup.CONFIG.doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
||||
if(scheduled) {
|
||||
if(nextBackup <= now) {
|
||||
TextileBackup.executorService.submit(
|
||||
|
@ -30,13 +30,13 @@ public class BackupScheduler {
|
|||
)
|
||||
);
|
||||
|
||||
nextBackup = now + TextileBackup.config.backupInterval;
|
||||
nextBackup = now + TextileBackup.CONFIG.backupInterval;
|
||||
}
|
||||
} else {
|
||||
nextBackup = now + TextileBackup.config.backupInterval;
|
||||
nextBackup = now + TextileBackup.CONFIG.backupInterval;
|
||||
scheduled = true;
|
||||
}
|
||||
} else if(!TextileBackup.config.doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
||||
} else if(!TextileBackup.CONFIG.doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
||||
if(scheduled && nextBackup <= now) {
|
||||
TextileBackup.executorService.submit(
|
||||
BackupHelper.create(
|
||||
|
|
|
@ -72,15 +72,15 @@ public class MakeBackupRunnable implements Runnable {
|
|||
|
||||
int coreCount;
|
||||
|
||||
if(TextileBackup.config.compressionCoreCountLimit <= 0) {
|
||||
if(TextileBackup.CONFIG.compressionCoreCountLimit <= 0) {
|
||||
coreCount = Runtime.getRuntime().availableProcessors();
|
||||
} else {
|
||||
coreCount = Math.min(TextileBackup.config.compressionCoreCountLimit, Runtime.getRuntime().availableProcessors());
|
||||
coreCount = Math.min(TextileBackup.CONFIG.compressionCoreCountLimit, Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
|
||||
TextileBackup.LOGGER.trace("Running compression on {} threads. Available cores = {}", coreCount, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
switch (TextileBackup.config.format) {
|
||||
switch (TextileBackup.CONFIG.format) {
|
||||
case ZIP:
|
||||
ParallelZipCompressor.createArchive(world, outFile, commandSource, coreCount);
|
||||
break;
|
||||
|
@ -98,7 +98,7 @@ public class MakeBackupRunnable implements Runnable {
|
|||
break;
|
||||
|
||||
default:
|
||||
TextileBackup.LOGGER.warn("Specified compressor ({}) is not supported! Zip will be used instead!", TextileBackup.config.format);
|
||||
TextileBackup.LOGGER.warn("Specified compressor ({}) is not supported! Zip will be used instead!", TextileBackup.CONFIG.format);
|
||||
Utilities.sendError("Error! No correct compression format specified! Using default compressor!", commandSource);
|
||||
|
||||
ParallelZipCompressor.createArchive(world, outFile, commandSource, coreCount);
|
||||
|
@ -113,6 +113,6 @@ public class MakeBackupRunnable implements Runnable {
|
|||
private String getFileName(){
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
return Utilities.getDateTimeFormatter().format(now) + (comment != null ? "#" + comment.replace("#", "") : "") + TextileBackup.config.format.getString();
|
||||
return Utilities.getDateTimeFormatter().format(now) + (comment != null ? "#" + comment.replace("#", "") : "") + TextileBackup.CONFIG.format.getString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public class ParallelZipCompressor {
|
|||
|
||||
arc.setMethod(ZipArchiveOutputStream.DEFLATED);
|
||||
arc.setUseZip64(Zip64Mode.AsNeeded);
|
||||
arc.setLevel(TextileBackup.config.compression);
|
||||
arc.setLevel(TextileBackup.CONFIG.compression);
|
||||
arc.setComment("Created on: " + Utilities.getDateTimeFormatter().format(LocalDateTime.now()));
|
||||
|
||||
File input = in.getCanonicalFile();
|
||||
|
|
|
@ -1,8 +1,29 @@
|
|||
/*
|
||||
A simple backup mod for Fabric
|
||||
Copyright (C) 2020 Szum123321
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core.restore;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.szum123321.textile_backup.LivingServer;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
import net.szum123321.textile_backup.core.create.BackupContext;
|
||||
import net.szum123321.textile_backup.core.create.BackupHelper;
|
||||
import net.szum123321.textile_backup.core.restore.decompressors.GenericTarDecompressor;
|
||||
import net.szum123321.textile_backup.core.restore.decompressors.ZipDecompressor;
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
|
||||
|
@ -10,59 +31,106 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
|
|||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileLock;
|
||||
|
||||
public class RestoreBackupRunnable implements Runnable {
|
||||
private final MinecraftServer server;
|
||||
private final File backupFile;
|
||||
private final String finalBackupComment;
|
||||
|
||||
public RestoreBackupRunnable(MinecraftServer server, File backupFile) {
|
||||
public RestoreBackupRunnable(MinecraftServer server, File backupFile, String finalBackupComment) {
|
||||
this.server = server;
|
||||
this.backupFile = backupFile;
|
||||
this.finalBackupComment = finalBackupComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(server.isRunning()) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
TextileBackup.LOGGER.error("Exception occurred!", e);
|
||||
}
|
||||
TextileBackup.LOGGER.info("Starting countdown...");
|
||||
waitDelay();
|
||||
|
||||
TextileBackup.LOGGER.info("Shutting down server...");
|
||||
server.stop(false);
|
||||
awaitServerShutdown();
|
||||
|
||||
if(TextileBackup.CONFIG.backupOldWorlds) {
|
||||
BackupHelper.create(
|
||||
new BackupContext.Builder()
|
||||
.setServer(server)
|
||||
.setInitiator(BackupContext.BackupInitiator.Restore)
|
||||
.setComment("Old_World" + (finalBackupComment != null ? "_" + finalBackupComment : ""))
|
||||
.build()
|
||||
).run();
|
||||
}
|
||||
|
||||
File worldFile = Utilities.getWorldFolder(server);
|
||||
|
||||
deleteDirectory(worldFile);
|
||||
TextileBackup.LOGGER.info("Deleting old world...");
|
||||
if(!deleteDirectory(worldFile))
|
||||
TextileBackup.LOGGER.error("Something went wrong while deleting old world!");
|
||||
|
||||
worldFile.mkdirs();
|
||||
|
||||
switch(Utilities.getFileExtension(backupFile).get()) {
|
||||
case ZIP:
|
||||
ZipDecompressor.decompress(backupFile, worldFile);
|
||||
break;
|
||||
try(FileInputStream fileInputStream = new FileInputStream(backupFile)) {
|
||||
TextileBackup.LOGGER.info("Starting decompression...");
|
||||
|
||||
case GZIP:
|
||||
GenericTarDecompressor.decompress(backupFile, worldFile, GzipCompressorInputStream.class);
|
||||
break;
|
||||
switch(Utilities.getFileExtension(backupFile).orElseThrow()) {
|
||||
case ZIP:
|
||||
ZipDecompressor.decompress(fileInputStream, worldFile);
|
||||
break;
|
||||
|
||||
case BZIP2:
|
||||
GenericTarDecompressor.decompress(backupFile, worldFile, BZip2CompressorInputStream.class);
|
||||
break;
|
||||
case GZIP:
|
||||
GenericTarDecompressor.decompress(fileInputStream, worldFile, GzipCompressorInputStream.class);
|
||||
break;
|
||||
|
||||
case LZMA:
|
||||
GenericTarDecompressor.decompress(backupFile, worldFile, XZCompressorInputStream.class);
|
||||
break;
|
||||
case BZIP2:
|
||||
GenericTarDecompressor.decompress(fileInputStream, worldFile, BZip2CompressorInputStream.class);
|
||||
break;
|
||||
|
||||
case LZMA:
|
||||
GenericTarDecompressor.decompress(fileInputStream, worldFile, XZCompressorInputStream.class);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
TextileBackup.LOGGER.error("Exception occurred!", e);
|
||||
}
|
||||
|
||||
TextileBackup.LOGGER.info("Done.");
|
||||
}
|
||||
|
||||
private static void deleteDirectory(File f) {
|
||||
private void waitDelay() {
|
||||
int delay = TextileBackup.CONFIG.restoreDelay;
|
||||
|
||||
while(delay > 0) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
delay--;
|
||||
} catch (InterruptedException e) {
|
||||
TextileBackup.LOGGER.error("Exception occurred!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void awaitServerShutdown() {
|
||||
while(((LivingServer)server).isAlive()) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
TextileBackup.LOGGER.error("Exception occurred!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean deleteDirectory(File f) {
|
||||
boolean state = true;
|
||||
|
||||
if(f.isDirectory()) {
|
||||
for(File f2 : f.listFiles())
|
||||
deleteDirectory(f2);
|
||||
state &= deleteDirectory(f2);
|
||||
}
|
||||
|
||||
f.delete();
|
||||
return f.delete() && state;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
A simple backup mod for Fabric
|
||||
Copyright (C) 2020 Szum123321
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core.restore;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
@ -14,10 +32,7 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
public class RestoreHelper {
|
||||
public static Runnable create(LocalDateTime backupTime, MinecraftServer server) {
|
||||
server.getPlayerManager().getPlayerList()
|
||||
.forEach(serverPlayerEntity -> serverPlayerEntity.sendMessage(new LiteralText("Warning! The server is going to shut down in few moments!"), false));
|
||||
|
||||
public static Thread create(LocalDateTime backupTime, MinecraftServer server, String comment) {
|
||||
File backupFile = Arrays.stream(Utilities.getBackupRootPath(Utilities.getLevelName(server))
|
||||
.listFiles())
|
||||
.filter(file -> Utilities.getFileCreationTime(file).isPresent())
|
||||
|
@ -25,24 +40,12 @@ public class RestoreHelper {
|
|||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
TextileBackup.LOGGER.info("Restoring: {}", backupFile.getName());
|
||||
server.getPlayerManager().getPlayerList()
|
||||
.forEach(serverPlayerEntity -> serverPlayerEntity.sendMessage(new LiteralText("Warning! The server is going to shut down in " + TextileBackup.CONFIG.restoreDelay + " seconds!"), false));
|
||||
|
||||
TextileBackup.globalShutdownBackupFlag.set(false);
|
||||
|
||||
BackupHelper.create(
|
||||
new BackupContext.Builder()
|
||||
.setServer(server)
|
||||
.setInitiator(BackupContext.BackupInitiator.Restore)
|
||||
.setComment("Old_World")
|
||||
.setSave()
|
||||
.build()
|
||||
).run();
|
||||
|
||||
TextileBackup.LOGGER.info("Shutting down server...");
|
||||
|
||||
server.stop(false);
|
||||
|
||||
return new RestoreBackupRunnable(server, backupFile);
|
||||
return new Thread(new RestoreBackupRunnable(server, backupFile, comment));
|
||||
}
|
||||
|
||||
public static List<RestoreableFile> getAvailableBackups(MinecraftServer server) {
|
||||
|
@ -61,8 +64,8 @@ public class RestoreHelper {
|
|||
private final String comment;
|
||||
|
||||
protected RestoreableFile(File file) {
|
||||
String extension = Utilities.getFileExtension(file).get().getString();
|
||||
this.creationTime = Utilities.getFileCreationTime(file).get();
|
||||
String extension = Utilities.getFileExtension(file).orElseThrow().getString();
|
||||
this.creationTime = Utilities.getFileCreationTime(file).orElseThrow();
|
||||
|
||||
final String filename = file.getName();
|
||||
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
A simple backup mod for Fabric
|
||||
Copyright (C) 2020 Szum123321
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core.restore.decompressors;
|
||||
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
|
@ -14,18 +32,19 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
|
||||
public class GenericTarDecompressor {
|
||||
public static void decompress(File archiveFile, File target, Class<? extends CompressorInputStream> DecompressorStream) {
|
||||
public static void decompress(FileInputStream fileInputStream, File target, Class<? extends CompressorInputStream> DecompressorStream) {
|
||||
Instant start = Instant.now();
|
||||
|
||||
try (FileInputStream inputStream = new FileInputStream(archiveFile);
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
|
||||
CompressorInputStream compressorInputStream = DecompressorStream.getDeclaredConstructor(InputStream.class).newInstance(bufferedInputStream);
|
||||
TarArchiveInputStream archiveInputStream = new TarArchiveInputStream(compressorInputStream)) {
|
||||
TarArchiveEntry entry;
|
||||
|
||||
while ((entry = archiveInputStream.getNextTarEntry()) != null) {
|
||||
if(!archiveInputStream.canReadEntryData(entry))
|
||||
if(!archiveInputStream.canReadEntryData(entry)) {
|
||||
TextileBackup.LOGGER.warn("Something when wrong while trying to decompress {}", entry.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
File file = target.toPath().resolve(entry.getName()).toFile();
|
||||
|
||||
|
@ -41,7 +60,7 @@ public class GenericTarDecompressor {
|
|||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
|
||||
IOUtils.copy(archiveInputStream, bufferedOutputStream);
|
||||
} catch (IOException e) {
|
||||
TextileBackup.LOGGER.error("An exception occurred while trying to compress file: " + file.getName(), e);
|
||||
TextileBackup.LOGGER.error("An exception occurred while trying to decompress file: " + file.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
A simple backup mod for Fabric
|
||||
Copyright (C) 2020 Szum123321
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core.restore.decompressors;
|
||||
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
|
@ -12,17 +30,18 @@ import java.time.Duration;
|
|||
import java.time.Instant;
|
||||
|
||||
public class ZipDecompressor {
|
||||
public static void decompress(File archiveFile, File target) {
|
||||
public static void decompress(FileInputStream fileInputStream, File target) {
|
||||
Instant start = Instant.now();
|
||||
|
||||
try (FileInputStream inputStream = new FileInputStream(archiveFile);
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
|
||||
ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream((bufferedInputStream))) {
|
||||
ZipArchiveEntry entry;
|
||||
|
||||
while ((entry = zipInputStream.getNextZipEntry()) != null) {
|
||||
if(!zipInputStream.canReadEntryData(entry))
|
||||
if(!zipInputStream.canReadEntryData(entry)){
|
||||
TextileBackup.LOGGER.warn("Something when wrong while trying to decompress {}", entry.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
File file = target.toPath().resolve(entry.getName()).toFile();
|
||||
|
||||
|
@ -38,7 +57,7 @@ public class ZipDecompressor {
|
|||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
|
||||
IOUtils.copy(zipInputStream, bufferedOutputStream);
|
||||
} catch (IOException e) {
|
||||
TextileBackup.LOGGER.error("An exception occurred while trying to compress file: " + file.getName(), e);
|
||||
TextileBackup.LOGGER.error("An exception occurred while trying to decompress file: " + file.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package net.szum123321.textile_backup.mixin;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.szum123321.textile_backup.LivingServer;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.core.create.BackupContext;
|
||||
import net.szum123321.textile_backup.core.create.BackupHelper;
|
||||
|
@ -28,10 +29,12 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
|||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MinecraftServer.class)
|
||||
public abstract class MinecraftServerMixin {
|
||||
public class MinecraftServerMixin implements LivingServer {
|
||||
private boolean isAlive = true;
|
||||
|
||||
@Inject(method = "shutdown", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/server/MinecraftServer;save(ZZZ)Z"))
|
||||
public void onFinalWorldSave(CallbackInfo ci) {
|
||||
if (TextileBackup.config.shutdownBackup && TextileBackup.globalShutdownBackupFlag.get())
|
||||
if (TextileBackup.CONFIG.shutdownBackup && TextileBackup.globalShutdownBackupFlag.get())
|
||||
TextileBackup.executorService.submit(
|
||||
BackupHelper.create(
|
||||
new BackupContext.Builder()
|
||||
|
@ -41,5 +44,12 @@ public abstract class MinecraftServerMixin {
|
|||
.build()
|
||||
)
|
||||
);
|
||||
|
||||
isAlive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return isAlive;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue