diff --git a/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java b/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java index d6ad376..be45b40 100644 --- a/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java +++ b/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java @@ -123,6 +123,14 @@ public class ConfigPOJO implements ConfigData { @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public ArchiveFormat format = ArchiveFormat.ZIP; + @Comment(""" + The Strict mode (default) aborts backup creation in case of any problem and deletes the created files + Permissible mode keeps partial/damaged backup but won't allow to restore it + Very Permissible mode will skip the verification process. THIS MOST CERTAINLY WILL LEAD TO DATA LOSS OR CORRUPTION + """) + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + public ErrorHandlingMode errorErrorHandlingMode = ErrorHandlingMode.STRICT; + @Comment("\nMinimal permission level required to run commands\n") @ConfigEntry.Category("Manage") @ConfigEntry.Gui.NoTooltip() @@ -177,6 +185,12 @@ public class ConfigPOJO implements ConfigData { } } + public enum ErrorHandlingMode { + STRICT, + PERMISSIBLE, + VERY_PERMISSIBLE + } + public enum ArchiveFormat { ZIP("zip"), GZIP("tar", "gz"), diff --git a/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java b/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java index a3481e3..c16fb90 100644 --- a/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java +++ b/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java @@ -1,17 +1,30 @@ +/* + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ + package net.szum123321.textile_backup.core; import java.io.Serializable; import java.nio.file.Path; import java.time.LocalDateTime; +import java.util.Map; -public record CompressionStatus(long[] treeHash, LocalDateTime date, long startTimestamp, long finishTimestamp, boolean ok, Path[] brokenFiles) implements Serializable { +public record CompressionStatus(long treeHash, LocalDateTime date, long startTimestamp, long finishTimestamp, Map brokenFiles) implements Serializable { public static final String DATA_FILENAME = "textile_status.data"; public boolean isValid(long decompressedHash) { return true; } - public static class Builder { - public synchronized void update(Path path, long hash, Exception error) { throw new RuntimeException("UNIMPLEMENTED!"); } - public synchronized void update(Path path, Exception error) { throw new RuntimeException("UNIMPLEMENTED!"); } - public synchronized void update(Path path, long hash) { throw new RuntimeException("UNIMPLEMENTED!"); } - public CompressionStatus build() { throw new RuntimeException("UNIMPLEMENTED!"); } - } } diff --git a/src/main/java/net/szum123321/textile_backup/core/DataLeftException.java b/src/main/java/net/szum123321/textile_backup/core/DataLeftException.java new file mode 100644 index 0000000..cc5c7b9 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/DataLeftException.java @@ -0,0 +1,25 @@ +/* + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ + +package net.szum123321.textile_backup.core; + +import java.io.IOException; + +public class DataLeftException extends IOException { + public DataLeftException(long n) { super("Input stream closed with " + n + " bytes left!"); } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/create/BrokenFileHandler.java b/src/main/java/net/szum123321/textile_backup/core/create/BrokenFileHandler.java new file mode 100644 index 0000000..be24aa9 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/create/BrokenFileHandler.java @@ -0,0 +1,36 @@ +/* + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ + +package net.szum123321.textile_backup.core.create; + +import org.spongepowered.include.com.google.common.collect.Maps; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class BrokenFileHandler { + private final HashMap store = Maps.newHashMap(); + public void handle(Path file, Exception e) { store.put(file, e); } + + public boolean valid() { return store.isEmpty(); } + + public Map get() { + return store; + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/create/FileInputStreamSupplier.java b/src/main/java/net/szum123321/textile_backup/core/create/FileInputStreamSupplier.java index 1f9daf8..695294e 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/FileInputStreamSupplier.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/FileInputStreamSupplier.java @@ -1,26 +1,26 @@ /* - A simple backup mod for Fabric - Copyright (C) 2022 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 . -*/ + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ package net.szum123321.textile_backup.core.create; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; -import net.szum123321.textile_backup.core.CompressionStatus; +import net.szum123321.textile_backup.core.FileTreeHashBuilder; import java.io.IOException; import java.io.InputStream; @@ -28,15 +28,16 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -public record FileInputStreamSupplier(Path path, String name, CompressionStatus.Builder builder) implements InputSupplier { +public record FileInputStreamSupplier(Path path, String name, FileTreeHashBuilder hashTreeBuilder, BrokenFileHandler brokenFileHandler) implements InputSupplier { private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); @Override public InputStream getInputStream() throws IOException { try { - return new HashingInputStream(Files.newInputStream(path), path, null, builder); + //TODO: select hashing algorithm! + return new HashingInputStream(Files.newInputStream(path), path, null, hashTreeBuilder, brokenFileHandler); } catch (IOException e) { - builder.update(path, e); + brokenFileHandler.handle(path, e); throw e; } } diff --git a/src/main/java/net/szum123321/textile_backup/core/create/HashingInputStream.java b/src/main/java/net/szum123321/textile_backup/core/create/HashingInputStream.java index 0b50904..532abca 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/HashingInputStream.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/HashingInputStream.java @@ -1,41 +1,46 @@ /* - A simple backup mod for Fabric - Copyright (C) 2022 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 . -*/ + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ package net.szum123321.textile_backup.core.create; -import net.szum123321.textile_backup.core.CompressionStatus; +import net.szum123321.textile_backup.core.DataLeftException; +import net.szum123321.textile_backup.core.FileTreeHashBuilder; import org.jetbrains.annotations.NotNull; import java.io.*; import java.nio.file.Path; import java.util.zip.Checksum; +//This class calculates a hash of the file on the input stream, submits it to FileTreeHashBuilder. +//In case the whole underlying stream hasn't been read, also puts it into BrokeFileHandler public class HashingInputStream extends FilterInputStream { private final Path path; private final Checksum hasher; - private final CompressionStatus.Builder statusBuilder; + private final FileTreeHashBuilder hashBuilder; + private final BrokenFileHandler brokenFileHandler; - public HashingInputStream(InputStream in, Path path, Checksum hasher, CompressionStatus.Builder statusBuilder) { + public HashingInputStream(InputStream in, Path path, Checksum hasher, FileTreeHashBuilder hashBuilder, BrokenFileHandler brokenFileHandler) { super(in); - this.hasher = hasher; - this.statusBuilder = statusBuilder; this.path = path; + this.hasher = hasher; + this.hashBuilder = hashBuilder; + this.brokenFileHandler = brokenFileHandler; } @Override @@ -54,8 +59,8 @@ public class HashingInputStream extends FilterInputStream { @Override public void close() throws IOException { - if(in.available() == 0) statusBuilder.update(path, hasher.getValue()); - else statusBuilder.update(path, hasher.getValue(), new RuntimeException("AAAaa")); + if(in.available() == 0) hashBuilder.update(path, hasher.getValue()); + else brokenFileHandler.handle(path, new DataLeftException(in.available())); super.close(); } } diff --git a/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnable.java b/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnable.java index f2184b9..e142173 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnable.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnable.java @@ -1,20 +1,20 @@ /* - 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 . -*/ + * A simple backup mod for Fabric + * Copyright (C) 2022 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 . + */ package net.szum123321.textile_backup.core.create; @@ -104,7 +104,7 @@ public class MakeBackupRunnable implements Runnable { case TAR -> new AbstractTarArchiver().createArchive(world, outFile, context, coreCount); } - new Cleanup(context.commandSource(), Utilities.getLevelName(context.server())).call(); + Globals.INSTANCE.getQueueExecutor().submit(new Cleanup(context.commandSource(), Utilities.getLevelName(context.server()))); if(config.get().broadcastBackupDone) { Utilities.notifyPlayers( diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/AbstractCompressor.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/AbstractCompressor.java index 7904234..f27a358 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/AbstractCompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/AbstractCompressor.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2020 Szum123321 + * Copyright (C) 2022 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 @@ -20,11 +20,11 @@ package net.szum123321.textile_backup.core.create.compressors; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; -import net.szum123321.textile_backup.core.ActionInitiator; -import net.szum123321.textile_backup.core.CompressionStatus; -import net.szum123321.textile_backup.core.NoSpaceLeftOnDeviceException; -import net.szum123321.textile_backup.core.Utilities; +import net.szum123321.textile_backup.config.ConfigHelper; +import net.szum123321.textile_backup.config.ConfigPOJO; +import net.szum123321.textile_backup.core.*; import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.BrokenFileHandler; import net.szum123321.textile_backup.core.create.FileInputStreamSupplier; import net.szum123321.textile_backup.core.create.InputSupplier; @@ -45,32 +45,53 @@ public abstract class AbstractCompressor { public void createArchive(Path inputFile, Path outputFile, BackupContext ctx, int coreLimit) { Instant start = Instant.now(); + FileTreeHashBuilder fileHashBuilder = new FileTreeHashBuilder(() -> null); //TODO: select hashing algorithm + BrokenFileHandler brokenFileHandler = new BrokenFileHandler(); + + boolean keep = true; + try (OutputStream outStream = Files.newOutputStream(outputFile); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outStream); OutputStream arc = createArchiveOutputStream(bufferedOutputStream, ctx, coreLimit); Stream fileStream = Files.walk(inputFile)) { - - CompressionStatus.Builder statusBuilder = new CompressionStatus.Builder(); - - fileStream + var it = fileStream .filter(path -> !Utilities.isBlacklisted(inputFile.relativize(path))) - .filter(Files::isRegularFile).forEach(file -> { - try { - addEntry(new FileInputStreamSupplier(file, inputFile.relativize(file).toString(), statusBuilder), arc); - } catch (IOException e) { - log.error("An exception occurred while trying to compress: {}", inputFile.relativize(file).toString(), e); + .filter(Files::isRegularFile).iterator(); - if (ctx.initiator() == ActionInitiator.Player) - log.sendError(ctx, "Something went wrong while compressing files!"); - } - }); + while(it.hasNext()) { + Path file = it.next(); + + try { + addEntry(new FileInputStreamSupplier(file, inputFile.relativize(file).toString(), fileHashBuilder, brokenFileHandler), arc); + } catch (Exception e) { + if(ConfigHelper.INSTANCE.get().errorErrorHandlingMode == ConfigPOJO.ErrorHandlingMode.STRICT) { + keep = false; + break; + } + brokenFileHandler.handle(file, e); + } + } + + //If there are still files left in the stream, we should handle them + while(it.hasNext()) { + Path file = it.next(); + brokenFileHandler.handle(file, new DataLeftException(Files.size(file))); + } + + Instant now = Instant.now(); + + CompressionStatus status = new CompressionStatus ( + fileHashBuilder.getValue(), + null, start.toEpochMilli(), now.toEpochMilli(), + brokenFileHandler.get() + ); //Serialize using gson? - ByteArrayOutputStream bo = new ByteArrayOutputStream(); - ObjectOutputStream o = new ObjectOutputStream(bo); - o.writeObject(statusBuilder.build()); - - addEntry(new StatusFileInputSupplier(bo.toByteArray(), bo.size()), arc); + try (ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream o = new ObjectOutputStream(bo)) { + o.writeObject(status); + addEntry(new StatusFileInputSupplier(bo.toByteArray()), arc); + } finish(arc); } catch(NoSpaceLeftOnDeviceException e) { @@ -85,16 +106,17 @@ public abstract class AbstractCompressor { log.sendError(ctx, "Backup failed. The file is corrupt."); log.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems"); } + if(ConfigHelper.INSTANCE.get().errorErrorHandlingMode == ConfigPOJO.ErrorHandlingMode.STRICT) keep = false; } catch (IOException | InterruptedException | ExecutionException e) { log.error("An exception occurred!", e); if(ctx.initiator() == ActionInitiator.Player) log.sendError(ctx, "Something went wrong while compressing files!"); + if(ConfigHelper.INSTANCE.get().errorErrorHandlingMode == ConfigPOJO.ErrorHandlingMode.STRICT) keep = false; + } finally { close(); } - // close(); - log.sendInfoAL(ctx, "Compression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); } @@ -109,17 +131,13 @@ public abstract class AbstractCompressor { //Same as above, just for ParallelGzipCompressor to shut down ExecutorService } - private record StatusFileInputSupplier(byte[] data, int len) implements InputSupplier { - @Override - public InputStream getInputStream() { return new ByteArrayInputStream(data, 0, len); } + private record StatusFileInputSupplier(byte[] data) implements InputSupplier { + public InputStream getInputStream() { return new ByteArrayInputStream(data); } - @Override public Path getPath() { return Path.of(CompressionStatus.DATA_FILENAME); } - @Override public String getName() { return CompressionStatus.DATA_FILENAME; } - @Override - public InputStream get() { return new ByteArrayInputStream(data, 0, len); } + public InputStream get() { return getInputStream(); } } } diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/ParallelZipCompressor.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/ParallelZipCompressor.java index 8d6f941..7ca5f67 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/ParallelZipCompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/ParallelZipCompressor.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2020 Szum123321 + * Copyright (C) 2022 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 @@ -99,7 +99,7 @@ public class ParallelZipCompressor extends ZipCompressor { if(!STACKTRACE_NO_SPACE_ON_LEFT_ON_DEVICE[i].equals(cause.getStackTrace()[i])) match = false; - //For clarity' sake let's not throw the ExecutionException itself rather only the cause, as the EE is just the wrapper + //For clarity's sake let's not throw the ExecutionException itself rather only the cause, as the EE is just the wrapper if(match) throw new NoSpaceLeftOnDeviceException(cause); } } diff --git a/src/main/resources/assets/textile_backup/lang/en_us.json b/src/main/resources/assets/textile_backup/lang/en_us.json index 7f92d1b..0c9ae5e 100644 --- a/src/main/resources/assets/textile_backup/lang/en_us.json +++ b/src/main/resources/assets/textile_backup/lang/en_us.json @@ -47,6 +47,9 @@ "text.autoconfig.textile_backup.option.format": "Archive and compression format", "text.autoconfig.textile_backup.option.format.@Tooltip": "See: https://github.com/Szum123321/textile_backup/wiki/Configuration#format", + "text.autoconfig.textile_backup.option.errorErrorHandlingMode": "Compression error handling mode", + "text.autoconfig.textile_backup.option.errorErrorHandlingMode.@Tooltip": "DO NOT ALTER unless fully certain", + "text.autoconfig.textile_backup.option.permissionLevel": "Min permission level", "text.autoconfig.textile_backup.option.alwaysSingleplayerAllowed": "Always allow on single-player", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2366667..a8b5d65 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -38,7 +38,7 @@ ], "depends": { - "fabricloader": ">=0.14.6", + "fabricloader": ">=0.14.0", "fabric": "*", "minecraft": ">=1.19.1", "cloth-config2": "*",