Should work now. Only a ton of testing left...

2.x-1.16
szymon 2020-08-03 23:38:54 +02:00
parent 7c7c4c7493
commit 227faef84c
17 changed files with 317 additions and 116 deletions

View File

@ -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!");

View File

@ -0,0 +1,5 @@
package net.szum123321.textile_backup;
public interface LivingServer {
boolean isAlive();
}

View File

@ -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())
));
}
}

View File

@ -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();

View File

@ -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;
});
}
}

View File

@ -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

View File

@ -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: ");

View File

@ -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);
}

View File

@ -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()) {

View File

@ -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(

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}