Merge pull request #102 from Szum123321/safe_restore

Safe restore
2.x-1.17
Szum123321 2022-06-22 20:04:25 +02:00 committed by GitHub
commit 267776789d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 320 additions and 288 deletions

View File

@ -6,7 +6,7 @@ yarn_mappings=1.19+build.4
loader_version=0.14.8 loader_version=0.14.8
#Fabric api #Fabric api
fabric_version=0.56.0+1.19 fabric_version=0.56.1+1.19
#Cloth Config #Cloth Config
cloth_version=7.0.72 cloth_version=7.0.72
@ -15,12 +15,12 @@ cloth_version=7.0.72
modmenu_version=4.0.0 modmenu_version=4.0.0
#Lazy DFU for faster dev start #Lazy DFU for faster dev start
lazydfu_version=0.1.2 lazydfu_version=v0.1.3
#Hash of commit form which parallel gzip will be build #Hash of commit form which parallel gzip will be build
pgzip_commit_hash=af5f5c297e735f3f2df7aa4eb0e19a5810b8aff6 pgzip_commit_hash=af5f5c297e735f3f2df7aa4eb0e19a5810b8aff6
# Mod Properties # Mod Properties
mod_version = 2.3.1 mod_version = 2.4.0
maven_group = net.szum123321 maven_group = net.szum123321
archives_base_name = textile_backup archives_base_name = textile_backup

View File

@ -20,7 +20,7 @@ package net.szum123321.textile_backup;
import net.szum123321.textile_backup.core.restore.AwaitThread; import net.szum123321.textile_backup.core.restore.AwaitThread;
import java.io.File; import java.nio.file.Path;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -35,6 +35,6 @@ public class Statics {
public static final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true); public static final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true);
public static boolean disableWatchdog = false; public static boolean disableWatchdog = false;
public static AwaitThread restoreAwaitThread = null; public static AwaitThread restoreAwaitThread = null;
public static Optional<File> untouchableFile = Optional.empty(); public static Optional<Path> untouchableFile = Optional.empty();
public static boolean disableTMPFiles = false; public static boolean disableTMPFiles = false;
} }

View File

@ -85,12 +85,16 @@ public class TextileLogger {
log(Level.ERROR, msg, data); log(Level.ERROR, msg, data);
} }
void error(String message, Throwable throwable) {
logger.error(prefix + message, throwable);
}
public void fatal(String msg, Object... data) { public void fatal(String msg, Object... data) {
log(Level.FATAL, msg, data); log(Level.FATAL, msg, data);
} }
boolean sendFeedback(Level level, ServerCommandSource source, String msg, Object... args) { boolean sendFeedback(Level level, ServerCommandSource source, String msg, Object... args) {
if(source != null && source.getEntity() instanceof PlayerEntity) { if(source != null && source.isExecutedByPlayer()) {
MutableText text = Text.literal(messageFactory.newMessage(msg, args).getFormattedMessage()); MutableText text = Text.literal(messageFactory.newMessage(msg, args).getFormattedMessage());
if(level.intLevel() == Level.TRACE.intLevel()) text.formatted(Formatting.GREEN); if(level.intLevel() == Level.TRACE.intLevel()) text.formatted(Formatting.GREEN);
@ -116,7 +120,7 @@ public class TextileLogger {
} }
public void sendInfo(BackupContext context, String msg, Object... args) { public void sendInfo(BackupContext context, String msg, Object... args) {
sendInfo(context.getCommandSource(), msg, args); sendInfo(context.commandSource(), msg, args);
} }
public void sendError(ServerCommandSource source, String msg, Object... args) { public void sendError(ServerCommandSource source, String msg, Object... args) {
@ -124,7 +128,7 @@ public class TextileLogger {
} }
public void sendError(BackupContext context, String msg, Object... args) { public void sendError(BackupContext context, String msg, Object... args) {
sendError(context.getCommandSource(), msg, args); sendError(context.commandSource(), msg, args);
} }
public void sendToPlayerAndLog(Level level, ServerCommandSource source, String msg, Object... args) { public void sendToPlayerAndLog(Level level, ServerCommandSource source, String msg, Object... args) {
@ -138,7 +142,7 @@ public class TextileLogger {
} }
public void sendInfoAL(BackupContext context, String msg, Object... args) { public void sendInfoAL(BackupContext context, String msg, Object... args) {
sendInfoAL(context.getCommandSource(), msg, args); sendInfoAL(context.commandSource(), msg, args);
} }
public void sendErrorAL(ServerCommandSource source, String msg, Object... args) { public void sendErrorAL(ServerCommandSource source, String msg, Object... args) {
@ -146,6 +150,6 @@ public class TextileLogger {
} }
public void sendErrorAL(BackupContext context, String msg, Object... args) { public void sendErrorAL(BackupContext context, String msg, Object... args) {
sendErrorAL(context.getCommandSource(), msg, args); sendErrorAL(context.commandSource(), msg, args);
} }
} }

View File

@ -21,7 +21,6 @@ package net.szum123321.textile_backup.commands.manage;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileBackup;
@ -31,11 +30,13 @@ import net.szum123321.textile_backup.Statics;
import net.szum123321.textile_backup.commands.FileSuggestionProvider; import net.szum123321.textile_backup.commands.FileSuggestionProvider;
import net.szum123321.textile_backup.core.Utilities; import net.szum123321.textile_backup.core.Utilities;
import java.io.File; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
public class DeleteCommand { public class DeleteCommand {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
@ -57,28 +58,28 @@ public class DeleteCommand {
throw CommandExceptions.DATE_TIME_PARSE_COMMAND_EXCEPTION_TYPE.create(e); throw CommandExceptions.DATE_TIME_PARSE_COMMAND_EXCEPTION_TYPE.create(e);
} }
File root = Utilities.getBackupRootPath(Utilities.getLevelName(source.getServer())); Path root = Utilities.getBackupRootPath(Utilities.getLevelName(source.getServer()));
Optional<File> optionalFile = Arrays.stream(root.listFiles()) try (Stream<Path> stream = Files.list(root)) {
.filter(Utilities::isValidBackup) stream.filter(Utilities::isValidBackup)
.filter(file -> Utilities.getFileCreationTime(file).orElse(LocalDateTime.MIN).equals(dateTime)) .filter(file -> Utilities.getFileCreationTime(file).orElse(LocalDateTime.MIN).equals(dateTime))
.findFirst(); .findFirst().ifPresent(file -> {
if(Statics.untouchableFile.isEmpty() || !Statics.untouchableFile.get().equals(file)) {
try {
Files.delete(file);
log.sendInfo(source, "File {} successfully deleted!", file);
if(optionalFile.isPresent()) { if(source.isExecutedByPlayer())
if(Statics.untouchableFile.isEmpty() || !Statics.untouchableFile.get().equals(optionalFile.get())) { log.info("Player {} deleted {}.", source.getPlayer().getName(), file);
if(optionalFile.get().delete()) { } catch (IOException e) {
log.sendInfo(source, "File {} successfully deleted!", optionalFile.get().getName()); log.sendError(source, "Something went wrong while deleting file!");
}
if(source.getEntity() instanceof PlayerEntity) } else {
log.info("Player {} deleted {}.", source.getPlayer().getName(), optionalFile.get().getName()); log.sendError(source, "Couldn't delete the file because it's being restored right now.");
} else { log.sendHint(source, "If you want to abort restoration then use: /backup killR");
log.sendError(source, "Something went wrong while deleting file!"); }
} });
} else { } catch (IOException ignored) {
log.sendError(source, "Couldn't delete the file because it's being restored right now.");
log.sendHint(source, "If you want to abort restoration then use: /backup killR");
}
} else {
log.sendError(source, "Couldn't find file by this name."); log.sendError(source, "Couldn't find file by this name.");
log.sendHint(source, "Maybe try /backup list"); log.sendHint(source, "Maybe try /backup list");
} }

View File

@ -82,7 +82,7 @@ public class RestoreBackupCommand {
Optional<RestoreHelper.RestoreableFile> backupFile = RestoreHelper.findFileAndLockIfPresent(dateTime, source.getServer()); Optional<RestoreHelper.RestoreableFile> backupFile = RestoreHelper.findFileAndLockIfPresent(dateTime, source.getServer());
if(backupFile.isPresent()) { if(backupFile.isPresent()) {
log.info("Found file to restore {}", backupFile.get().getFile().getName()); log.info("Found file to restore {}", backupFile.get().getFile().getFileName().toString());
} else { } else {
log.sendInfo(source, "No file created on {} was found!", dateTime.format(Statics.defaultDateTimeFormatter)); log.sendInfo(source, "No file created on {} was found!", dateTime.format(Statics.defaultDateTimeFormatter));

View File

@ -32,18 +32,20 @@ import net.szum123321.textile_backup.config.ConfigPOJO;
import net.szum123321.textile_backup.Statics; import net.szum123321.textile_backup.Statics;
import net.szum123321.textile_backup.mixin.MinecraftServerSessionAccessor; import net.szum123321.textile_backup.mixin.MinecraftServerSessionAccessor;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.file.SimplePathVisitor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.time.*; import java.time.*;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
public class Utilities { public class Utilities {
private final static ConfigHelper config = ConfigHelper.INSTANCE; private final static ConfigHelper config = ConfigHelper.INSTANCE;
@ -60,28 +62,47 @@ public class Utilities {
return ((MinecraftServerSessionAccessor)server).getSession().getDirectoryName(); return ((MinecraftServerSessionAccessor)server).getSession().getDirectoryName();
} }
public static File getWorldFolder(MinecraftServer server) { public static Path getWorldFolder(MinecraftServer server) {
return ((MinecraftServerSessionAccessor)server) return ((MinecraftServerSessionAccessor)server)
.getSession() .getSession()
.getWorldDirectory(World.OVERWORLD) .getWorldDirectory(World.OVERWORLD);
.toFile();
} }
public static File getBackupRootPath(String worldName) { public static Path getBackupRootPath(String worldName) {
File path = new File(config.get().path).getAbsoluteFile(); Path path = Path.of(config.get().path).toAbsolutePath();
if (config.get().perWorldBackup) path = path.toPath().resolve(worldName).toFile(); if (config.get().perWorldBackup) path = path.resolve(worldName);
if (!path.exists()) path.mkdirs(); try {
Files.createDirectories(path);
} catch (IOException e) {
//I REALLY shouldn't be handling this here
}
return path; return path;
} }
public static void deleteDirectory(Path path) throws IOException {
Files.walkFileTree(path, new SimplePathVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
public static void updateTMPFSFlag(MinecraftServer server) { public static void updateTMPFSFlag(MinecraftServer server) {
boolean flag = false; boolean flag = false;
Path tmp_dir = Path.of(System.getProperty("java.io.tmpdir")); Path tmp_dir = Path.of(System.getProperty("java.io.tmpdir"));
if( if(
FileUtils.sizeOfDirectory(Utilities.getWorldFolder(server)) >= FileUtils.sizeOfDirectory(Utilities.getWorldFolder(server).toFile()) >=
tmp_dir.toFile().getUsableSpace() tmp_dir.toFile().getUsableSpace()
) { ) {
log.error("Not enough space left in TMP directory! ({})", tmp_dir); log.error("Not enough space left in TMP directory! ({})", tmp_dir);
@ -130,11 +151,11 @@ public class Utilities {
.findAny(); .findAny();
} }
public static Optional<ConfigPOJO.ArchiveFormat> getArchiveExtension(File f) { public static Optional<ConfigPOJO.ArchiveFormat> getArchiveExtension(Path f) {
return getArchiveExtension(f.getName()); return getArchiveExtension(f.getFileName().toString());
} }
public static Optional<LocalDateTime> getFileCreationTime(File file) { public static Optional<LocalDateTime> getFileCreationTime(Path file) {
LocalDateTime creationTime = null; LocalDateTime creationTime = null;
if(getArchiveExtension(file).isPresent()) { if(getArchiveExtension(file).isPresent()) {
@ -143,7 +164,7 @@ public class Utilities {
try { try {
creationTime = LocalDateTime.from( creationTime = LocalDateTime.from(
Utilities.getDateTimeFormatter().parse( Utilities.getDateTimeFormatter().parse(
file.getName().split(fileExtension)[0].split("#")[0] file.getFileName().toString().split(fileExtension)[0].split("#")[0]
) )
); );
} catch (Exception ignored) {} } catch (Exception ignored) {}
@ -152,7 +173,7 @@ public class Utilities {
try { try {
creationTime = LocalDateTime.from( creationTime = LocalDateTime.from(
Utilities.getBackupDateTimeFormatter().parse( Utilities.getBackupDateTimeFormatter().parse(
file.getName().split(fileExtension)[0].split("#")[0] file.getFileName().toString().split(fileExtension)[0].split("#")[0]
) )
); );
} catch (Exception ignored2){} } catch (Exception ignored2){}
@ -160,7 +181,7 @@ public class Utilities {
if(creationTime == null) { if(creationTime == null) {
try { try {
FileTime fileTime = (FileTime) Files.getAttribute(file.toPath(), "creationTime"); FileTime fileTime = (FileTime) Files.getAttribute(file, "creationTime");
creationTime = LocalDateTime.ofInstant(fileTime.toInstant(), ZoneOffset.systemDefault()); creationTime = LocalDateTime.ofInstant(fileTime.toInstant(), ZoneOffset.systemDefault());
} catch (IOException ignored3) {} } catch (IOException ignored3) {}
} }
@ -169,7 +190,7 @@ public class Utilities {
return Optional.ofNullable(creationTime); return Optional.ofNullable(creationTime);
} }
public static boolean isValidBackup(File f) { public static boolean isValidBackup(Path f) {
return getArchiveExtension(f).isPresent() && getFileCreationTime(f).isPresent() && isFileOk(f); return getArchiveExtension(f).isPresent() && getFileCreationTime(f).isPresent() && isFileOk(f);
} }
@ -177,6 +198,10 @@ public class Utilities {
return f.exists() && f.isFile(); return f.exists() && f.isFile();
} }
public static boolean isFileOk(Path f) {
return Files.exists(f) && Files.isRegularFile(f);
}
public static DateTimeFormatter getDateTimeFormatter() { public static DateTimeFormatter getDateTimeFormatter() {
return DateTimeFormatter.ofPattern(config.get().dateTimeFormat); return DateTimeFormatter.ofPattern(config.get().dateTimeFormat);
} }

View File

@ -33,18 +33,6 @@ public record BackupContext(@NotNull MinecraftServer server,
boolean save, boolean save,
String comment) { String comment) {
public MinecraftServer getServer() {
return server;
}
public ServerCommandSource getCommandSource() {
return commandSource;
}
public ActionInitiator getInitiator() {
return initiator;
}
public boolean startedByPlayer() { public boolean startedByPlayer() {
return initiator == ActionInitiator.Player; return initiator == ActionInitiator.Player;
} }
@ -53,14 +41,6 @@ public record BackupContext(@NotNull MinecraftServer server,
return save; return save;
} }
public String getComment() {
return comment;
}
public UUID getInitiatorUUID() {
return initiator.equals(ActionInitiator.Player) && commandSource.getEntity() != null ? commandSource.getEntity().getUuid(): Util.NIL_UUID;
}
public static class Builder { public static class Builder {
private MinecraftServer server; private MinecraftServer server;
private ServerCommandSource commandSource; private ServerCommandSource commandSource;

View File

@ -24,13 +24,16 @@ import net.szum123321.textile_backup.TextileBackup;
import net.szum123321.textile_backup.TextileLogger; import net.szum123321.textile_backup.TextileLogger;
import net.szum123321.textile_backup.config.ConfigHelper; import net.szum123321.textile_backup.config.ConfigHelper;
import net.szum123321.textile_backup.core.Utilities; import net.szum123321.textile_backup.core.Utilities;
import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
public class BackupHelper { public class BackupHelper {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
@ -38,7 +41,7 @@ public class BackupHelper {
public static Runnable create(BackupContext ctx) { public static Runnable create(BackupContext ctx) {
if(config.get().broadcastBackupStart) { if(config.get().broadcastBackupStart) {
Utilities.notifyPlayers(ctx.getServer(), Utilities.notifyPlayers(ctx.server(),
"Warning! Server backup will begin shortly. You may experience some lag." "Warning! Server backup will begin shortly. You may experience some lag."
); );
} else { } else {
@ -49,12 +52,12 @@ public class BackupHelper {
builder.append("Backup started "); builder.append("Backup started ");
builder.append(ctx.getInitiator().getPrefix()); builder.append(ctx.initiator().getPrefix());
if(ctx.startedByPlayer()) if(ctx.startedByPlayer())
builder.append(ctx.getCommandSource().getDisplayName().getString()); builder.append(ctx.commandSource().getDisplayName().getString());
else else
builder.append(ctx.getInitiator().getName()); builder.append(ctx.initiator().getName());
builder.append(" on: "); builder.append(" on: ");
builder.append(Utilities.getDateTimeFormatter().format(LocalDateTime.now())); builder.append(Utilities.getDateTimeFormatter().format(LocalDateTime.now()));
@ -64,10 +67,10 @@ public class BackupHelper {
if (ctx.shouldSave()) { if (ctx.shouldSave()) {
log.sendInfoAL(ctx, "Saving server..."); log.sendInfoAL(ctx, "Saving server...");
ctx.getServer().getPlayerManager().saveAllPlayerData(); ctx.server().getPlayerManager().saveAllPlayerData();
try { try {
ctx.getServer().save(false, true, true); ctx.server().save(false, true, true);
} catch (Exception e) { } catch (Exception e) {
log.sendErrorAL(ctx,"An exception occurred when trying to save the world!"); log.sendErrorAL(ctx,"An exception occurred when trying to save the world!");
} }
@ -77,52 +80,105 @@ public class BackupHelper {
} }
public static int executeFileLimit(ServerCommandSource ctx, String worldName) { public static int executeFileLimit(ServerCommandSource ctx, String worldName) {
File root = Utilities.getBackupRootPath(worldName); Path root = Utilities.getBackupRootPath(worldName);
int deletedFiles = 0; int deletedFiles = 0;
if (root.isDirectory() && root.exists() && root.listFiles() != null) {
if (Files.isDirectory(root) && Files.exists(root) && !isEmpty(root)) {
if (config.get().maxAge > 0) { // delete files older that configured if (config.get().maxAge > 0) { // delete files older that configured
final LocalDateTime now = LocalDateTime.now(); final LocalDateTime now = LocalDateTime.now();
deletedFiles += Arrays.stream(root.listFiles()) try(Stream<Path> stream = Files.list(root)) {
.filter(Utilities::isValidBackup)// We check if we can get file's creation date so that the next line won't throw an exception deletedFiles += stream
.filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > config.get().maxAge) .filter(Utilities::isValidBackup)// We check if we can get file's creation date so that the next line won't throw an exception
.map(f -> deleteFile(f, ctx)) .filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > config.get().maxAge)
.filter(b -> b).count(); //a bit awkward .mapToInt(f -> deleteFile(f, ctx))
.sum();
} catch (IOException e) {
log.error("An exception occurred while trying to delete old files!", e);
}
} }
if (config.get().backupsToKeep > 0 && root.listFiles().length > config.get().backupsToKeep) { int noToKeep = config.get().backupsToKeep > 0 ? config.get().backupsToKeep : Integer.MAX_VALUE;
deletedFiles += Arrays.stream(root.listFiles()) long maxSize = config.get().maxSize > 0 ? config.get().maxSize : Long.MAX_VALUE;
.filter(Utilities::isValidBackup)
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime((File) f).get()).reversed())
.skip(config.get().backupsToKeep)
.map(f -> deleteFile(f, ctx))
.filter(b -> b).count();
}
if (config.get().maxSize > 0 && FileUtils.sizeOfDirectory(root) / 1024 > config.get().maxSize) { AtomicInteger currentNo = new AtomicInteger(countBackups(root));
deletedFiles += Arrays.stream(root.listFiles()) AtomicLong currentSize = new AtomicLong(countSize(root));
try(Stream<Path> stream = Files.list(root)) {
deletedFiles += stream
.filter(Utilities::isValidBackup) .filter(Utilities::isValidBackup)
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime(f).get())) .sorted(Comparator.comparing(f -> Utilities.getFileCreationTime(f).get()))
.takeWhile(f -> FileUtils.sizeOfDirectory(root) / 1024 > config.get().maxSize) .takeWhile(f -> (currentNo.get() > noToKeep) || (currentSize.get() > maxSize))
.map(f -> deleteFile(f, ctx)) .peek(f -> currentNo.decrementAndGet())
.filter(b -> b).count(); .peek(f -> {
try {
currentSize.addAndGet(Files.size(f));
} catch (IOException e) {
currentSize.set(0);
}
})
.mapToInt(f -> deleteFile(f, ctx))
.sum();
} catch (IOException e) {
log.error("An exception occurred while trying to delete old files!", e);
} }
} }
return deletedFiles; return deletedFiles;
} }
private static boolean deleteFile(File f, ServerCommandSource ctx) { private static int countBackups(Path path) {
if(Statics.untouchableFile.isEmpty()|| !Statics.untouchableFile.get().equals(f)) { try(Stream<Path> stream = Files.list(path)) {
if(f.delete()) { return (int) stream
log.sendInfoAL(ctx, "Deleting: {}", f.getName()); .filter(Utilities::isValidBackup)
return true; .count();
} else { } catch (IOException ignored) {}
log.sendErrorAL(ctx, "Something went wrong while deleting: {}.", f.getName()); return 0;
}
private static long countSize(Path path) {
try(Stream<Path> stream = Files.list(path)) {
return (int) stream
.filter(Utilities::isValidBackup)
.mapToLong(f -> {
try {
return Files.size(f);
} catch (IOException e) {
return 0;
}
})
.sum();
} catch (IOException ignored) {}
return 0;
}
private static boolean isEmpty(Path path) {
if (Files.isDirectory(path)) {
try (Stream<Path> entries = Files.list(path)) {
return entries.findFirst().isEmpty();
} catch (IOException e) {
return false;
} }
} }
return false; return false;
} }
//1 -> ok, 0 -> err
private static int deleteFile(Path f, ServerCommandSource ctx) {
if(Statics.untouchableFile.isEmpty()|| !Statics.untouchableFile.get().equals(f)) {
try {
Files.delete(f);
log.sendInfoAL(ctx, "Deleting: {}", f);
} catch (IOException e) {
if(ctx.isExecutedByPlayer()) log.sendError(ctx, "Something went wrong while deleting: {}.", f);
log.error("Something went wrong while deleting: {}.", f, e);
return 0;
}
return 1;
}
return 0;
}
} }

View File

@ -30,9 +30,10 @@ import net.szum123321.textile_backup.core.create.compressors.tar.ParallelBZip2Co
import net.szum123321.textile_backup.core.create.compressors.tar.ParallelGzipCompressor; import net.szum123321.textile_backup.core.create.compressors.tar.ParallelGzipCompressor;
import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream; import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class MakeBackupRunnable implements Runnable { public class MakeBackupRunnable implements Runnable {
@ -48,33 +49,30 @@ public class MakeBackupRunnable implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
Utilities.disableWorldSaving(context.getServer()); Utilities.disableWorldSaving(context.server());
Statics.disableWatchdog = true; Statics.disableWatchdog = true;
Utilities.updateTMPFSFlag(context.getServer()); Utilities.updateTMPFSFlag(context.server());
log.sendInfoAL(context, "Starting backup"); log.sendInfoAL(context, "Starting backup");
File world = Utilities.getWorldFolder(context.getServer()); Path world = Utilities.getWorldFolder(context.server());
log.trace("Minecraft world is: {}", world); log.trace("Minecraft world is: {}", world);
File outFile = Utilities Path outFile = Utilities
.getBackupRootPath(Utilities.getLevelName(context.getServer())) .getBackupRootPath(Utilities.getLevelName(context.server()))
.toPath() .resolve(getFileName());
.resolve(getFileName())
.toFile();
log.trace("Outfile is: {}", outFile); log.trace("Outfile is: {}", outFile);
outFile.getParentFile().mkdirs();
try { try {
outFile.createNewFile(); Files.createDirectories(outFile.getParent());
Files.createFile(outFile);
} catch (IOException e) { } catch (IOException e) {
log.error("An exception occurred when trying to create new backup file!", e); log.error("An exception occurred when trying to create new backup file!", e);
if(context.getInitiator() == ActionInitiator.Player) if(context.initiator() == ActionInitiator.Player)
log.sendError(context, "An exception occurred when trying to create new backup file!"); log.sendError(context, "An exception occurred when trying to create new backup file!");
return; return;
@ -92,10 +90,13 @@ public class MakeBackupRunnable implements Runnable {
switch (config.get().format) { switch (config.get().format) {
case ZIP -> { case ZIP -> {
if (coreCount > 1 && !Statics.disableTMPFiles) if (coreCount > 1 && !Statics.disableTMPFiles) {
ParallelZipCompressor.getInstance().createArchive(world, outFile, context, coreCount); ParallelZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
else log.trace("Using PARALLEL Zip Compressor. Threads: {}", coreCount);
} else {
ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount); ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
log.trace("Using REGULAR Zip Compressor. Threads: {}");
}
} }
case BZIP2 -> ParallelBZip2Compressor.getInstance().createArchive(world, outFile, context, coreCount); case BZIP2 -> ParallelBZip2Compressor.getInstance().createArchive(world, outFile, context, coreCount);
case GZIP -> ParallelGzipCompressor.getInstance().createArchive(world, outFile, context, coreCount); case GZIP -> ParallelGzipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
@ -105,26 +106,20 @@ public class MakeBackupRunnable implements Runnable {
} }
}.createArchive(world, outFile, context, coreCount); }.createArchive(world, outFile, context, coreCount);
case TAR -> new AbstractTarArchiver().createArchive(world, outFile, context, coreCount); case TAR -> new AbstractTarArchiver().createArchive(world, outFile, context, coreCount);
default -> {
log.warn("Specified compressor ({}) is not supported! Zip will be used instead!", config.get().format);
if (context.getInitiator() == ActionInitiator.Player)
log.sendError(context.getCommandSource(), "Error! No correct compression format specified! Using default compressor!");
ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
}
} }
BackupHelper.executeFileLimit(context.getCommandSource(), Utilities.getLevelName(context.getServer())); BackupHelper.executeFileLimit(context.commandSource(), Utilities.getLevelName(context.server()));
if(config.get().broadcastBackupDone) { if(config.get().broadcastBackupDone) {
Utilities.notifyPlayers( Utilities.notifyPlayers(
context.getServer(), context.server(),
"Done!" "Done!"
); );
} else { } else {
log.sendInfoAL(context, "Done!"); log.sendInfoAL(context, "Done!");
} }
} finally { } finally {
Utilities.enableWorldSaving(context.getServer()); Utilities.enableWorldSaving(context.server());
Statics.disableWatchdog = false; Statics.disableWatchdog = false;
} }
} }
@ -133,7 +128,7 @@ public class MakeBackupRunnable implements Runnable {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
return Utilities.getDateTimeFormatter().format(now) + return Utilities.getDateTimeFormatter().format(now) +
(context.getComment() != null ? "#" + context.getComment().replaceAll("[\\\\/:*?\"<>|#]", "") : "") + (context.comment() != null ? "#" + context.comment().replaceAll("[\\\\/:*?\"<>|#]", "") : "") +
config.get().format.getCompleteString(); config.get().format.getCompleteString();
} }
} }

View File

@ -31,29 +31,29 @@ import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
public abstract class AbstractCompressor { public abstract class AbstractCompressor {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
public void createArchive(File inputFile, File outputFile, BackupContext ctx, int coreLimit) { public void createArchive(Path inputFile, Path outputFile, BackupContext ctx, int coreLimit) {
Instant start = Instant.now(); Instant start = Instant.now();
try (FileOutputStream outStream = new FileOutputStream(outputFile); try (OutputStream outStream = Files.newOutputStream(outputFile);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outStream); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outStream);
OutputStream arc = createArchiveOutputStream(bufferedOutputStream, ctx, coreLimit)) { OutputStream arc = createArchiveOutputStream(bufferedOutputStream, ctx, coreLimit);
Stream<Path> fileStream = Files.walk(inputFile)) {
Files.walk(inputFile.toPath()) fileStream
.filter(path -> !Utilities.isBlacklisted(inputFile.toPath().relativize(path))) .filter(path -> !Utilities.isBlacklisted(inputFile.relativize(path)))
.map(Path::toFile) .filter(Files::isRegularFile).forEach(file -> {
.filter(File::isFile)
.forEach(file -> {
try { try {
//hopefully one broken file won't spoil the whole archive //hopefully one broken file won't spoil the whole archive
addEntry(file, inputFile.toPath().relativize(file.toPath()).toString(), arc); addEntry(file, inputFile.relativize(file).toString(), arc);
} catch (IOException e) { } catch (IOException e) {
log.error("An exception occurred while trying to compress: {}", inputFile.toPath().relativize(file.toPath()).toString(), e); log.error("An exception occurred while trying to compress: {}", inputFile.relativize(file).toString(), e);
if (ctx.getInitiator() == ActionInitiator.Player) if (ctx.initiator() == ActionInitiator.Player)
log.sendError(ctx, "Something went wrong while compressing files!"); log.sendError(ctx, "Something went wrong while compressing files!");
} }
}); });
@ -67,14 +67,14 @@ public abstract class AbstractCompressor {
For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems
In case this isn't it here's also the exception itself""", e); In case this isn't it here's also the exception itself""", e);
if(ctx.getInitiator() == ActionInitiator.Player) { if(ctx.initiator() == ActionInitiator.Player) {
log.sendError(ctx, "Backup failed. The file is corrupt."); log.sendError(ctx, "Backup failed. The file is corrupt.");
log.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems"); log.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems");
} }
} catch (IOException | InterruptedException | ExecutionException e) { } catch (IOException | InterruptedException | ExecutionException e) {
log.error("An exception occurred!", e); log.error("An exception occurred!", e);
} catch (Exception e) { } catch (Exception e) {
if(ctx.getInitiator() == ActionInitiator.Player) if(ctx.initiator() == ActionInitiator.Player)
log.sendError(ctx, "Something went wrong while compressing files!"); log.sendError(ctx, "Something went wrong while compressing files!");
} finally { } finally {
close(); close();
@ -86,13 +86,13 @@ public abstract class AbstractCompressor {
} }
protected abstract OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException; protected abstract OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException;
protected abstract void addEntry(File file, String entryName, OutputStream arc) throws IOException; protected abstract void addEntry(Path file, String entryName, OutputStream arc) throws IOException;
protected void finish(OutputStream arc) throws InterruptedException, ExecutionException, IOException { protected void finish(OutputStream arc) throws InterruptedException, ExecutionException, IOException {
;//Basically this function is only needed for the ParallelZipCompressor to write out ParallelScatterZipCreator //Basically this function is only needed for the ParallelZipCompressor to write out ParallelScatterZipCreator
} }
protected void close() { protected void close() {
;//Same as above, just for ParallelGzipCompressor to shutdown ExecutorService //Same as above, just for ParallelGzipCompressor to shut down ExecutorService
} }
} }

View File

@ -26,6 +26,8 @@ import org.apache.commons.compress.archivers.zip.*;
import org.apache.commons.compress.parallel.InputStreamSupplier; import org.apache.commons.compress.parallel.InputStreamSupplier;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -65,13 +67,13 @@ public class ParallelZipCompressor extends ZipCompressor {
} }
@Override @Override
protected void addEntry(File file, String entryName, OutputStream arc) throws IOException { protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException {
ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName); ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName);
if(ZipCompressor.isDotDat(file.getName())) { if(ZipCompressor.isDotDat(file.getFileName().toString())) {
entry.setMethod(ZipArchiveOutputStream.STORED); entry.setMethod(ZipEntry.STORED);
entry.setSize(file.length()); entry.setSize(Files.size(file));
entry.setCompressedSize(file.length()); entry.setCompressedSize(Files.size(file));
entry.setCrc(getCRC(file)); entry.setCrc(getCRC(file));
} else entry.setMethod(ZipEntry.DEFLATED); } else entry.setMethod(ZipEntry.DEFLATED);
@ -126,12 +128,12 @@ public class ParallelZipCompressor extends ZipCompressor {
} }
} }
record FileInputStreamSupplier(File sourceFile) implements InputStreamSupplier { record FileInputStreamSupplier(Path sourceFile) implements InputStreamSupplier {
public InputStream get() { public InputStream get() {
try { try {
return new FileInputStream(sourceFile); return Files.newInputStream(sourceFile);
} catch (IOException e) { } catch (IOException e) {
log.error("An exception occurred while trying to create an input stream from file: {}!", sourceFile.getName(), e); log.error("An exception occurred while trying to create an input stream from file: {}!", sourceFile.toString(), e);
} }
return null; return null;

View File

@ -27,9 +27,12 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.IOUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;
import java.util.zip.ZipEntry;
public class ZipCompressor extends AbstractCompressor { public class ZipCompressor extends AbstractCompressor {
private final static ConfigHelper config = ConfigHelper.INSTANCE; private final static ConfigHelper config = ConfigHelper.INSTANCE;
@ -51,14 +54,14 @@ public class ZipCompressor extends AbstractCompressor {
} }
@Override @Override
protected void addEntry(File file, String entryName, OutputStream arc) throws IOException { protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)){ try (InputStream fileInputStream = Files.newInputStream(file)){
ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName); ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName);
if(isDotDat(file.getName())) { if(isDotDat(file.getFileName().toString())) {
entry.setMethod(ZipArchiveOutputStream.STORED); entry.setMethod(ZipEntry.STORED);
entry.setSize(file.length()); entry.setSize(Files.size(file));
entry.setCompressedSize(file.length()); entry.setCompressedSize(Files.size(file));
entry.setCrc(getCRC(file)); entry.setCrc(getCRC(file));
} }
@ -76,15 +79,15 @@ public class ZipCompressor extends AbstractCompressor {
return arr[arr.length - 1].contains("dat"); //includes dat_old return arr[arr.length - 1].contains("dat"); //includes dat_old
} }
protected static long getCRC(File file) throws IOException { protected static long getCRC(Path file) throws IOException {
Checksum sum = new CRC32(); Checksum sum = new CRC32();
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int len; int len;
try (InputStream stream = new FileInputStream(file)) { try (InputStream stream = Files.newInputStream(file)) {
while ((len = stream.read(buffer)) != -1) sum.update(buffer, 0, len); while ((len = stream.read(buffer)) != -1) sum.update(buffer, 0, len);
} catch (IOException e) { } catch (IOException e) {
throw new IOException("Error while calculating CRC of: " + file.getAbsolutePath(), e); throw new IOException("Error while calculating CRC of: " + file.toAbsolutePath(), e);
} }
return sum.getValue(); return sum.getValue();

View File

@ -24,10 +24,9 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.IOUtils;
import java.io.File; import java.io.*;
import java.io.FileInputStream; import java.nio.file.Files;
import java.io.IOException; import java.nio.file.Path;
import java.io.OutputStream;
public class AbstractTarArchiver extends AbstractCompressor { public class AbstractTarArchiver extends AbstractCompressor {
protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException {
@ -44,8 +43,8 @@ public class AbstractTarArchiver extends AbstractCompressor {
} }
@Override @Override
protected void addEntry(File file, String entryName, OutputStream arc) throws IOException { protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)){ try (InputStream fileInputStream = Files.newInputStream(file)){
TarArchiveEntry entry = (TarArchiveEntry)((TarArchiveOutputStream) arc).createArchiveEntry(file, entryName); TarArchiveEntry entry = (TarArchiveEntry)((TarArchiveOutputStream) arc).createArchiveEntry(file, entryName);
((TarArchiveOutputStream)arc).putArchiveEntry(entry); ((TarArchiveOutputStream)arc).putArchiveEntry(entry);

View File

@ -31,7 +31,9 @@ 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.GenericTarDecompressor;
import net.szum123321.textile_backup.core.restore.decompressors.ZipDecompressor; import net.szum123321.textile_backup.core.restore.decompressors.ZipDecompressor;
import java.io.File; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class RestoreBackupRunnable implements Runnable { public class RestoreBackupRunnable implements Runnable {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
@ -49,54 +51,57 @@ public class RestoreBackupRunnable implements Runnable {
log.info("Shutting down server..."); log.info("Shutting down server...");
ctx.getServer().stop(false); ctx.server().stop(false);
awaitServerShutdown(); awaitServerShutdown();
if(config.get().backupOldWorlds) { if(config.get().backupOldWorlds) {
BackupHelper.create( BackupHelper.create(
BackupContext.Builder BackupContext.Builder
.newBackupContextBuilder() .newBackupContextBuilder()
.setServer(ctx.getServer()) .setServer(ctx.server())
.setInitiator(ActionInitiator.Restore) .setInitiator(ActionInitiator.Restore)
.setComment("Old_World" + (ctx.getComment() != null ? "_" + ctx.getComment() : "")) .setComment("Old_World" + (ctx.comment() != null ? "_" + ctx.comment() : ""))
.build() .build()
).run(); ).run();
} }
File worldFile = Utilities.getWorldFolder(ctx.getServer()); Path worldFile = Utilities.getWorldFolder(ctx.server());
log.info("Deleting old world..."); try {
Path tmp = Files.createTempDirectory(
worldFile.getParent(),
ctx.restoreableFile().getFile().getFileName().toString());
if(!deleteDirectory(worldFile)) log.info("Starting decompression...");
log.error("Something went wrong while deleting old world!");
worldFile.mkdirs(); if (ctx.restoreableFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP)
ZipDecompressor.decompress(ctx.restoreableFile().getFile(), tmp);
else
GenericTarDecompressor.decompress(ctx.restoreableFile().getFile(), tmp);
log.info("Starting decompression..."); log.info("Deleting old world...");
if(ctx.getFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP) Utilities.deleteDirectory(worldFile);
ZipDecompressor.decompress(ctx.getFile().getFile(), worldFile);
else
GenericTarDecompressor.decompress(ctx.getFile().getFile(), worldFile);
if(config.get().deleteOldBackupAfterRestore) { Files.move(tmp, worldFile);
log.info("Deleting old backup");
if(!ctx.getFile().getFile().delete()) log.info("Something went wrong while deleting old backup"); if (config.get().deleteOldBackupAfterRestore) {
log.info("Deleting old backup");
Files.delete(ctx.restoreableFile().getFile());
}
} catch (IOException e) {
log.error("An exception occurred while trying to restore a backup!", e);
} }
//in case we're playing on client //in case we're playing on client
Statics.globalShutdownBackupFlag.set(true); Statics.globalShutdownBackupFlag.set(true);
log.info("Done!"); log.info("Done!");
//Might solve #37
//Idk if it's a good idea...
//Runtime.getRuntime().exit(0);
} }
private void awaitServerShutdown() { private void awaitServerShutdown() {
while(((LivingServer)ctx.getServer()).isAlive()) { while(((LivingServer)ctx.server()).isAlive()) {
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -104,15 +109,4 @@ public class RestoreBackupRunnable implements Runnable {
} }
} }
} }
private static boolean deleteDirectory(File f) {
boolean state = true;
if(f.isDirectory()) {
for(File f2 : f.listFiles())
state &= deleteDirectory(f2);
}
return f.delete() && state;
}
} }

View File

@ -21,43 +21,15 @@ package net.szum123321.textile_backup.core.restore;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.util.Util;
import net.szum123321.textile_backup.core.ActionInitiator; import net.szum123321.textile_backup.core.ActionInitiator;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
public record RestoreContext(RestoreHelper.RestoreableFile file, public record RestoreContext(RestoreHelper.RestoreableFile restoreableFile,
MinecraftServer server, MinecraftServer server,
@Nullable String comment, @Nullable String comment,
ActionInitiator initiator, ActionInitiator initiator,
ServerCommandSource commandSource) { ServerCommandSource commandSource) {
public RestoreHelper.RestoreableFile getFile() {
return file;
}
public MinecraftServer getServer() {
return server;
}
@Nullable
public String getComment() {
return comment;
}
public ActionInitiator getInitiator() {
return initiator;
}
public UUID getInitiatorUUID() {
return initiator.equals(ActionInitiator.Player) && commandSource.getEntity() != null ? commandSource.getEntity().getUuid(): Util.NIL_UUID;
}
public ServerCommandSource getCommandSource() {
return commandSource;
}
public static final class Builder { public static final class Builder {
private RestoreHelper.RestoreableFile file; private RestoreHelper.RestoreableFile file;
private MinecraftServer server; private MinecraftServer server;

View File

@ -28,23 +28,31 @@ import net.szum123321.textile_backup.core.ActionInitiator;
import net.szum123321.textile_backup.core.Utilities; import net.szum123321.textile_backup.core.Utilities;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RestoreHelper { public class RestoreHelper {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
private final static ConfigHelper config = ConfigHelper.INSTANCE; private final static ConfigHelper config = ConfigHelper.INSTANCE;
public static Optional<RestoreableFile> findFileAndLockIfPresent(LocalDateTime backupTime, MinecraftServer server) { public static Optional<RestoreableFile> findFileAndLockIfPresent(LocalDateTime backupTime, MinecraftServer server) {
File root = Utilities.getBackupRootPath(Utilities.getLevelName(server)); Path root = Utilities.getBackupRootPath(Utilities.getLevelName(server));
Optional<RestoreableFile> optionalFile = Arrays.stream(root.listFiles()) Optional<RestoreableFile> optionalFile;
.map(RestoreableFile::newInstance) try (Stream<Path> stream = Files.list(root)) {
.flatMap(Optional::stream) optionalFile = stream
.filter(rf -> rf.getCreationTime().equals(backupTime)) .map(RestoreableFile::newInstance)
.findFirst(); .flatMap(Optional::stream)
.filter(rf -> rf.getCreationTime().equals(backupTime))
.findFirst();
} catch (IOException e) {
throw new RuntimeException(e);
}
Statics.untouchableFile = optionalFile.map(RestoreableFile::getFile); Statics.untouchableFile = optionalFile.map(RestoreableFile::getFile);
@ -52,8 +60,8 @@ public class RestoreHelper {
} }
public static AwaitThread create(RestoreContext ctx) { public static AwaitThread create(RestoreContext ctx) {
if(ctx.getInitiator() == ActionInitiator.Player) if(ctx.initiator() == ActionInitiator.Player)
log.info("Backup restoration was initiated by: {}", ctx.getCommandSource().getName()); log.info("Backup restoration was initiated by: {}", ctx.commandSource().getName());
else else
log.info("Backup restoration was initiated form Server Console"); log.info("Backup restoration was initiated form Server Console");
@ -69,28 +77,32 @@ public class RestoreHelper {
} }
public static List<RestoreableFile> getAvailableBackups(MinecraftServer server) { public static List<RestoreableFile> getAvailableBackups(MinecraftServer server) {
File root = Utilities.getBackupRootPath(Utilities.getLevelName(server)); Path root = Utilities.getBackupRootPath(Utilities.getLevelName(server));
return Arrays.stream(root.listFiles()) try (Stream<Path> stream = Files.list(root)) {
.filter(Utilities::isValidBackup) return stream.filter(Utilities::isValidBackup)
.map(RestoreableFile::newInstance) .map(RestoreableFile::newInstance)
.flatMap(Optional::stream) .flatMap(Optional::stream)
.collect(Collectors.toList()); .collect(Collectors.toList());
} catch (IOException e) {
log.error("Error while listing available backups", e);
return new LinkedList<>();
}
} }
public static class RestoreableFile implements Comparable<RestoreableFile> { public static class RestoreableFile implements Comparable<RestoreableFile> {
private final File file; private final Path file;
private final ConfigPOJO.ArchiveFormat archiveFormat; private final ConfigPOJO.ArchiveFormat archiveFormat;
private final LocalDateTime creationTime; private final LocalDateTime creationTime;
private final String comment; private final String comment;
private RestoreableFile(File file) throws NoSuchElementException { private RestoreableFile(Path file) throws NoSuchElementException {
this.file = file; this.file = file;
archiveFormat = Utilities.getArchiveExtension(file).orElseThrow(() -> new NoSuchElementException("Couldn't get file extension!")); archiveFormat = Utilities.getArchiveExtension(file).orElseThrow(() -> new NoSuchElementException("Couldn't get file extension!"));
String extension = archiveFormat.getCompleteString(); String extension = archiveFormat.getCompleteString();
creationTime = Utilities.getFileCreationTime(file).orElseThrow(() -> new NoSuchElementException("Couldn't get file creation time!")); creationTime = Utilities.getFileCreationTime(file).orElseThrow(() -> new NoSuchElementException("Couldn't get file creation time!"));
final String filename = file.getName(); final String filename = file.getFileName().toString();
if(filename.split("#").length > 1) { if(filename.split("#").length > 1) {
this.comment = filename.split("#")[1].split(extension)[0]; this.comment = filename.split("#")[1].split(extension)[0];
@ -99,7 +111,7 @@ public class RestoreHelper {
} }
} }
public static Optional<RestoreableFile> newInstance(File file) { public static Optional<RestoreableFile> newInstance(Path file) {
try { try {
return Optional.of(new RestoreableFile(file)); return Optional.of(new RestoreableFile(file));
} catch (NoSuchElementException ignored) {} } catch (NoSuchElementException ignored) {}
@ -107,7 +119,7 @@ public class RestoreHelper {
return Optional.empty(); return Optional.empty();
} }
public File getFile() { public Path getFile() {
return file; return file;
} }

View File

@ -29,16 +29,17 @@ import org.apache.commons.compress.utils.IOUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
public class GenericTarDecompressor { public class GenericTarDecompressor {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
public static void decompress(File input, File target) { public static void decompress(Path input, Path target) throws IOException {
Instant start = Instant.now(); Instant start = Instant.now();
try (InputStream fileInputStream = new FileInputStream(input); try (InputStream fileInputStream = Files.newInputStream(input);
InputStream bufferedInputStream = new BufferedInputStream(fileInputStream); InputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
InputStream compressorInputStream = getCompressorInputStream(bufferedInputStream); InputStream compressorInputStream = getCompressorInputStream(bufferedInputStream);
TarArchiveInputStream archiveInputStream = new TarArchiveInputStream(compressorInputStream)) { TarArchiveInputStream archiveInputStream = new TarArchiveInputStream(compressorInputStream)) {
@ -50,27 +51,20 @@ public class GenericTarDecompressor {
continue; continue;
} }
File file = target.toPath().resolve(entry.getName()).toFile(); Path file = target.resolve(entry.getName());
if(entry.isDirectory()) { if(entry.isDirectory()) {
file.mkdirs(); Files.createDirectories(file);
} else { } else {
File parent = file.getParentFile(); Files.createDirectories(file.getParent());
try (OutputStream outputStream = Files.newOutputStream(file);
if (!parent.isDirectory() && !parent.mkdirs()) { BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
log.error("Failed to create {}", parent); IOUtils.copy(archiveInputStream, bufferedOutputStream);
} else {
try (OutputStream outputStream = Files.newOutputStream(file.toPath());
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
IOUtils.copy(archiveInputStream, bufferedOutputStream);
} catch (IOException e) {
log.error("An exception occurred while trying to decompress file: {}", file.getName(), e);
}
} }
} }
} }
} catch (IOException | CompressorException e) { } catch (CompressorException e) {
log.error("An exception occurred! ", e); throw new IOException(e);
} }
log.info("Decompression took {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); log.info("Decompression took {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));

View File

@ -21,43 +21,38 @@ package net.szum123321.textile_backup.core.restore.decompressors;
import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileBackup;
import net.szum123321.textile_backup.TextileLogger; import net.szum123321.textile_backup.TextileLogger;
import net.szum123321.textile_backup.core.Utilities; import net.szum123321.textile_backup.core.Utilities;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.IOUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Iterator;
public class ZipDecompressor { public class ZipDecompressor {
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
public static void decompress(File inputFile, File target) { public static void decompress(Path inputFile, Path target) throws IOException {
Instant start = Instant.now(); Instant start = Instant.now();
try(ZipFile zipFile = new ZipFile(inputFile)) { try(ZipFile zipFile = new ZipFile(inputFile.toFile())) {
zipFile.getEntries().asIterator().forEachRemaining(entry -> { for (Iterator<ZipArchiveEntry> it = zipFile.getEntries().asIterator(); it.hasNext(); ) {
File file = target.toPath().resolve(entry.getName()).toFile(); ZipArchiveEntry entry = it.next();
Path file = target.resolve(entry.getName());
if(entry.isDirectory()) { if(entry.isDirectory()) {
file.mkdirs(); Files.createDirectories(file);
} else { } else {
File parent = file.getParentFile(); Files.createDirectories(file.getParent());
try (OutputStream outputStream = Files.newOutputStream(file);
if (!parent.isDirectory() && !parent.mkdirs()) { BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
log.error("Failed to create {}", parent); IOUtils.copy(zipFile.getInputStream(entry), bufferedOutputStream);
} else {
try (OutputStream outputStream = Files.newOutputStream(file.toPath());
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
IOUtils.copy(zipFile.getInputStream(entry), bufferedOutputStream);
} catch (IOException e) {
log.error("An exception occurred while trying to decompress file: {}", entry.getName(), e);
}
} }
} }
}); }
} catch (IOException e) {
log.error("An exception occurred! ", e);
} }
log.info("Decompression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); log.info("Decompression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));