diff --git a/build.gradle b/build.gradle index 72e04cd..5dcdb51 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.0-SNAPSHOT' + id 'fabric-loom' version '1.2-SNAPSHOT' id 'maven-publish' } @@ -17,17 +17,6 @@ repositories { mavenCentral() } -loom { - runs { - testServer { - server() - ideConfigGenerated project.rootProject == project - name = "Testmod Server" - source sourceSets.test - } - } -} - dependencies { //to change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" @@ -46,8 +35,8 @@ dependencies { modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}") //General compression library - implementation "org.apache.commons:commons-compress:1.21" - include "org.apache.commons:commons-compress:1.21" + implementation "org.apache.commons:commons-compress:1.22" + include "org.apache.commons:commons-compress:1.22" //LZMA support implementation 'org.tukaani:xz:1.9' @@ -116,7 +105,7 @@ publishing { } static def getMcMinor(ver) { - String[] arr = ((String)ver).split("\\.") + String[] arr = ((String)ver).split("[.-]") if(arr.length < 2) return ver diff --git a/gradle.properties b/gradle.properties index 3ed2600..0bbd976 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,26 +1,25 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx1G -minecraft_version=1.19.2 -yarn_mappings=1.19.2+build.28 -loader_version=0.14.10 +minecraft_version=1.20-rc1 +yarn_mappings=1.20-rc1+build.2 +loader_version=0.14.21 #Fabric api -fabric_version=0.64.0+1.19.2 +fabric_version=0.83.0+1.20 #Cloth Config -cloth_version=8.2.88 +cloth_version=11.0.98 #ModMenu -modmenu_version=4.1.0 +modmenu_version=7.0.0-beta.2 -#Lazy DFU for faster dev start -lazydfu_version=v0.1.3 +databreaker_version=0.2.10 #Hash of commit form which parallel gzip will be build pgzip_commit_hash=af5f5c297e735f3f2df7aa4eb0e19a5810b8aff6 # Mod Properties -mod_version = 2.5.0 +mod_version = 3.0.0 maven_group = net.szum123321 archives_base_name = textile_backup \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..fae0804 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/net/szum123321/textile_backup/Globals.java b/src/main/java/net/szum123321/textile_backup/Globals.java index 8d33251..2567528 100644 --- a/src/main/java/net/szum123321/textile_backup/Globals.java +++ b/src/main/java/net/szum123321/textile_backup/Globals.java @@ -1,30 +1,33 @@ /* - * A simple backup mod for Fabric - * Copyright (C) 2022 Szum123321 + * 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 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 . + * 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; import net.minecraft.server.MinecraftServer; +import net.szum123321.textile_backup.core.digest.BalticHash; +import net.szum123321.textile_backup.core.digest.Hash; import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.MakeBackupRunnable; + import net.szum123321.textile_backup.core.restore.AwaitThread; import org.apache.commons.io.FileUtils; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.time.format.DateTimeFormatter; @@ -34,19 +37,48 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.zip.CRC32; public class Globals { public static final Globals INSTANCE = new Globals(); - private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - public final static DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); + private static final TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); + public static final DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); + public static final Supplier CHECKSUM_SUPPLIER = BalticHash::new;/*() -> new Hash() { + private final CRC32 crc = new CRC32(); + + @Override + public void update ( int b){ + crc.update(b); + } + + @Override + public void update ( long b) { + ByteBuffer v = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + v.putLong(b); + crc.update(v.array()); + } + + @Override + public void update ( byte[] b, int off, int len){ + crc.update(b, off, len); + } - private ExecutorService executorService = null;// = Executors.newSingleThreadExecutor(); + @Override + public long getValue () { + return crc.getValue(); + } + };*/ + + private ExecutorService executorService = null;//TODO: AAAAAAAAAAAAAAA MEMORY LEAK!!!!!!!!! public final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true); public boolean disableWatchdog = false; private boolean disableTMPFiles = false; private AwaitThread restoreAwaitThread = null; private Path lockedPath = null; + private String combinedVersionString; + private Globals() {} public ExecutorService getQueueExecutor() { return executorService; } @@ -64,8 +96,8 @@ public void shutdownQueueExecutor(long timeout) { if(!executorService.awaitTermination(timeout, TimeUnit.MICROSECONDS)) { log.error("Timeout occurred while waiting for currently running backups to finish!"); executorService.shutdownNow().stream() - .filter(r -> r instanceof MakeBackupRunnable) - .map(r -> (MakeBackupRunnable)r) + // .filter(r -> r instanceof ExecutableBackup) + // .map(r -> (ExecutableBackup)r) .forEach(r -> log.error("Dropping: {}", r.toString())); if(!executorService.awaitTermination(1000, TimeUnit.MICROSECONDS)) log.error("Couldn't shut down the executor!"); @@ -84,8 +116,8 @@ public void shutdownQueueExecutor(long timeout) { public Optional getLockedFile() { return Optional.ofNullable(lockedPath); } public void setLockedFile(Path p) { lockedPath = p; } - public boolean disableTMPFS() { return disableTMPFiles; } - public void updateTMPFSFlag(MinecraftServer server) { + public synchronized boolean disableTMPFS() { return disableTMPFiles; } + public synchronized void updateTMPFSFlag(MinecraftServer server) { disableTMPFiles = false; Path tmp_dir = Path.of(System.getProperty("java.io.tmpdir")); if( @@ -103,4 +135,12 @@ public void updateTMPFSFlag(MinecraftServer server) { if(disableTMPFiles) log.error("Might cause: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems"); } + + public String getCombinedVersionString() { + return combinedVersionString; + } + + public void setCombinedVersionString(String combinedVersionString) { + this.combinedVersionString = combinedVersionString; + } } diff --git a/src/main/java/net/szum123321/textile_backup/TextileBackup.java b/src/main/java/net/szum123321/textile_backup/TextileBackup.java index d1e313b..876bc5b 100644 --- a/src/main/java/net/szum123321/textile_backup/TextileBackup.java +++ b/src/main/java/net/szum123321/textile_backup/TextileBackup.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; @@ -26,6 +26,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; import net.szum123321.textile_backup.commands.create.CleanupCommand; import net.szum123321.textile_backup.commands.create.StartBackupCommand; @@ -38,9 +39,9 @@ import net.szum123321.textile_backup.config.ConfigHelper; import net.szum123321.textile_backup.config.ConfigPOJO; import net.szum123321.textile_backup.core.ActionInitiator; -import net.szum123321.textile_backup.core.create.BackupContext; import net.szum123321.textile_backup.core.create.BackupScheduler; -import net.szum123321.textile_backup.core.create.MakeBackupRunnableFactory; +import net.szum123321.textile_backup.core.create.ExecutableBackup; +import net.szum123321.textile_backup.test.BalticHashTest; public class TextileBackup implements ModInitializer { public static final String MOD_NAME = "Textile Backup"; @@ -51,7 +52,13 @@ public class TextileBackup implements ModInitializer { @Override public void onInitialize() { - log.info("Starting Textile Backup by Szum123321"); + Globals.INSTANCE.setCombinedVersionString( + FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().getMetadata().getVersion().getFriendlyString() + + ":" + + FabricLoader.getInstance().getModContainer("minecraft").orElseThrow().getMetadata().getVersion().getFriendlyString() + ); + + log.info("Starting Textile Backup {} by Szum123321", Globals.INSTANCE.getCombinedVersionString()); ConfigHelper.updateInstance(AutoConfig.register(ConfigPOJO.class, JanksonConfigSerializer::new)); @@ -63,19 +70,21 @@ public void onInitialize() { Globals.INSTANCE.updateTMPFSFlag(server); }); - //Wait 60s for already submited backups to finish. After that kill the bastards and run the one last if required + //Wait 60s for already submitted backups to finish. After that kill the bastards and run the one last if required ServerLifecycleEvents.SERVER_STOPPED.register(server -> { Globals.INSTANCE.shutdownQueueExecutor(60000); if (config.get().shutdownBackup && Globals.INSTANCE.globalShutdownBackupFlag.get()) { - MakeBackupRunnableFactory.create( - BackupContext.Builder - .newBackupContextBuilder() - .setServer(server) - .setInitiator(ActionInitiator.Shutdown) - .setComment("shutdown") - .build() - ).run(); + try { + ExecutableBackup.Builder + .newBackupContextBuilder() + .setServer(server) + .setInitiator(ActionInitiator.Shutdown) + .setComment("shutdown") + .announce() + .build() + .call(); + } catch (Exception ignored) {} } }); diff --git a/src/main/java/net/szum123321/textile_backup/TextileLogger.java b/src/main/java/net/szum123321/textile_backup/TextileLogger.java index 6d2815a..08b5dc7 100644 --- a/src/main/java/net/szum123321/textile_backup/TextileLogger.java +++ b/src/main/java/net/szum123321/textile_backup/TextileLogger.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 @@ -23,7 +23,7 @@ import net.minecraft.text.MutableText; import net.minecraft.util.Formatting; import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -94,7 +94,7 @@ boolean sendFeedback(Level level, ServerCommandSource source, String msg, Object else if(level.intLevel() <= Level.WARN.intLevel()) text.formatted(Formatting.RED); else text.formatted(Formatting.WHITE); - source.sendFeedback(prefixText.copy().append(text), false); + source.sendFeedback(() -> prefixText.copy().append(text), false); return true; } else { @@ -112,7 +112,7 @@ public void sendInfo(ServerCommandSource source, String msg, Object... args) { sendFeedback(Level.INFO, source, msg, args); } - public void sendInfo(BackupContext context, String msg, Object... args) { + public void sendInfo(ExecutableBackup context, String msg, Object... args) { sendInfo(context.commandSource(), msg, args); } @@ -120,7 +120,8 @@ public void sendError(ServerCommandSource source, String msg, Object... args) { sendFeedback(Level.ERROR, source, msg, args); } - public void sendError(BackupContext context, String msg, Object... args) { + + public void sendError(ExecutableBackup context, String msg, Object... args) { sendError(context.commandSource(), msg, args); } @@ -134,7 +135,7 @@ public void sendInfoAL(ServerCommandSource source, String msg, Object... args) { sendToPlayerAndLog(Level.INFO, source, msg, args); } - public void sendInfoAL(BackupContext context, String msg, Object... args) { + public void sendInfoAL(ExecutableBackup context, String msg, Object... args) { sendInfoAL(context.commandSource(), msg, args); } @@ -142,7 +143,7 @@ public void sendErrorAL(ServerCommandSource source, String msg, Object... args) sendToPlayerAndLog(Level.ERROR, source, msg, args); } - public void sendErrorAL(BackupContext context, String msg, Object... args) { + public void sendErrorAL(ExecutableBackup context, String msg, Object... args) { sendErrorAL(context.commandSource(), msg, args); } } diff --git a/src/main/java/net/szum123321/textile_backup/client/ModMenuEntry.java b/src/main/java/net/szum123321/textile_backup/client/ModMenuEntry.java index 75017a2..ddb2c04 100644 --- a/src/main/java/net/szum123321/textile_backup/client/ModMenuEntry.java +++ b/src/main/java/net/szum123321/textile_backup/client/ModMenuEntry.java @@ -1,20 +1,19 @@ /* - * A simple backup mod for Fabric - * Copyright (C) 2022 Szum123321 + * 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 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 . + * 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.client; diff --git a/src/main/java/net/szum123321/textile_backup/commands/CommandExceptions.java b/src/main/java/net/szum123321/textile_backup/commands/CommandExceptions.java index 3c31542..e0009d0 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/CommandExceptions.java +++ b/src/main/java/net/szum123321/textile_backup/commands/CommandExceptions.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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/FileSuggestionProvider.java b/src/main/java/net/szum123321/textile_backup/commands/FileSuggestionProvider.java index 319e341..da2ec52 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/FileSuggestionProvider.java +++ b/src/main/java/net/szum123321/textile_backup/commands/FileSuggestionProvider.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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/create/CleanupCommand.java b/src/main/java/net/szum123321/textile_backup/commands/create/CleanupCommand.java index c0567ee..765e4ae 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/create/CleanupCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/create/CleanupCommand.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.commands.create; diff --git a/src/main/java/net/szum123321/textile_backup/commands/create/StartBackupCommand.java b/src/main/java/net/szum123321/textile_backup/commands/create/StartBackupCommand.java index 830a88a..7964c5a 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/create/StartBackupCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/create/StartBackupCommand.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.commands.create; @@ -25,8 +25,7 @@ import net.szum123321.textile_backup.Globals; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; -import net.szum123321.textile_backup.core.create.BackupContext; -import net.szum123321.textile_backup.core.create.MakeBackupRunnableFactory; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import javax.annotation.Nullable; @@ -41,22 +40,15 @@ public static LiteralArgumentBuilder register() { } private static int execute(ServerCommandSource source, @Nullable String comment) { - try { - Globals.INSTANCE.getQueueExecutor().submit( - MakeBackupRunnableFactory.create( - BackupContext.Builder - .newBackupContextBuilder() - .setCommandSource(source) - .setComment(comment) - .guessInitiator() - .saveServer() - .build() - ) - ); - } catch (Exception e) { - log.error("Something went wrong while executing command!", e); - throw e; - } + Globals.INSTANCE.getQueueExecutor().submit( + ExecutableBackup.Builder + .newBackupContextBuilder() + .setCommandSource(source) + .setComment(comment) + .guessInitiator() + .saveServer() + .build() + ); return 1; } diff --git a/src/main/java/net/szum123321/textile_backup/commands/manage/BlacklistCommand.java b/src/main/java/net/szum123321/textile_backup/commands/manage/BlacklistCommand.java index 5baaedd..6e138be 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/manage/BlacklistCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/manage/BlacklistCommand.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/manage/DeleteCommand.java b/src/main/java/net/szum123321/textile_backup/commands/manage/DeleteCommand.java index 5fc0279..f1c4d0c 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/manage/DeleteCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/manage/DeleteCommand.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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/manage/ListBackupsCommand.java b/src/main/java/net/szum123321/textile_backup/commands/manage/ListBackupsCommand.java index 962709e..c9b4656 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/manage/ListBackupsCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/manage/ListBackupsCommand.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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/manage/WhitelistCommand.java b/src/main/java/net/szum123321/textile_backup/commands/manage/WhitelistCommand.java index c5da2f4..728e62b 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/manage/WhitelistCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/manage/WhitelistCommand.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/restore/KillRestoreCommand.java b/src/main/java/net/szum123321/textile_backup/commands/restore/KillRestoreCommand.java index f0578a0..f63efc8 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/restore/KillRestoreCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/restore/KillRestoreCommand.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 diff --git a/src/main/java/net/szum123321/textile_backup/commands/restore/RestoreBackupCommand.java b/src/main/java/net/szum123321/textile_backup/commands/restore/RestoreBackupCommand.java index b53f18a..b65e166 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/restore/RestoreBackupCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/restore/RestoreBackupCommand.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 diff --git a/src/main/java/net/szum123321/textile_backup/config/ConfigHelper.java b/src/main/java/net/szum123321/textile_backup/config/ConfigHelper.java index 07332bd..704c082 100644 --- a/src/main/java/net/szum123321/textile_backup/config/ConfigHelper.java +++ b/src/main/java/net/szum123321/textile_backup/config/ConfigHelper.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 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..7ab41ca 100644 --- a/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java +++ b/src/main/java/net/szum123321/textile_backup/config/ConfigPOJO.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 @@ -18,16 +18,17 @@ package net.szum123321.textile_backup.config; -import blue.endless.jankson.annotation.SerializedName; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.annotation.SerializedName; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.annotation.Config; import me.shedaniel.autoconfig.annotation.ConfigEntry; -import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; import net.szum123321.textile_backup.TextileBackup; import java.time.format.DateTimeFormatter; import java.util.*; +//TODO: Remove BZIP2 and LZMA compressors. As for the popular vote @Config(name = TextileBackup.MOD_ID) public class ConfigPOJO implements ConfigData { @Comment("\nShould every world have its own backup folder?\n") @@ -90,7 +91,7 @@ public class ConfigPOJO implements ConfigData { public long maxAge = 0; @Comment(""" - \nMaximum size of backup folder in kilo bytes (1024). + \nMaximum size of backup folder in kibi bytes (1024). If set to 0 then backups will not be deleted """) @ConfigEntry.Gui.Tooltip() @@ -114,8 +115,6 @@ public class ConfigPOJO implements ConfigData { \nAvailable formats are: ZIP - normal zip archive using standard deflate compression GZIP - tar.gz using gzip compression - BZIP2 - tar.bz2 archive using bzip2 compression - LZMA - tar.xz using lzma compression TAR - .tar with no compression """) @ConfigEntry.Gui.Tooltip() @@ -162,6 +161,14 @@ public class ConfigPOJO implements ConfigData { @ConfigEntry.Gui.Tooltip() public String dateTimeFormat = "yyyy.MM.dd_HH-mm-ss"; + @Comment(""" + \nThe Strict mode (default) aborts backup creation in case of any problem and deletes 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 IntegrityVerificationMode integrityVerificationMode = IntegrityVerificationMode.STRICT; + @Override public void validatePostLoad() throws ValidationException { if(compressionCoreCountLimit > Runtime.getRuntime().availableProcessors()) @@ -177,6 +184,16 @@ public void validatePostLoad() throws ValidationException { } } + public enum IntegrityVerificationMode { + STRICT, + PERMISSIBLE, + VERY_PERMISSIBLE; + + public boolean isStrict() { return this == STRICT; } + + public boolean verify() { return this != VERY_PERMISSIBLE; } + } + public enum ArchiveFormat { ZIP("zip"), GZIP("tar", "gz"), diff --git a/src/main/java/net/szum123321/textile_backup/core/ActionInitiator.java b/src/main/java/net/szum123321/textile_backup/core/ActionInitiator.java index 07375b9..21e58aa 100644 --- a/src/main/java/net/szum123321/textile_backup/core/ActionInitiator.java +++ b/src/main/java/net/szum123321/textile_backup/core/ActionInitiator.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 @@ -26,9 +26,7 @@ public enum ActionInitiator { ServerConsole("Server Console", "from"), //some/ting typed a command and it was not a player (command blocks and server console count) Timer("Timer", "by"), //a.k.a scheduler Shutdown("Server Shutdown", "by"), - Restore("Backup Restoration", "because of"), - Null("Null (That shouldn't have happened)", "form"); - + Restore("Backup Restoration", "because of"); private final String name; private final String prefix; diff --git a/src/main/java/net/szum123321/textile_backup/core/Cleanup.java b/src/main/java/net/szum123321/textile_backup/core/Cleanup.java index 56fd97d..6347b61 100644 --- a/src/main/java/net/szum123321/textile_backup/core/Cleanup.java +++ b/src/main/java/net/szum123321/textile_backup/core/Cleanup.java @@ -1,20 +1,19 @@ /* - * A simple backup mod for Fabric - * Copyright (C) 2022 Szum123321 + * 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 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 . + * 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; diff --git a/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java b/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java new file mode 100644 index 0000000..de5a2b3 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/CompressionStatus.java @@ -0,0 +1,100 @@ +/* + * 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 net.szum123321.textile_backup.core.restore.RestoreContext; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Optional; + +public record CompressionStatus(long treeHash, Map brokenFiles, LocalDateTime date, long startTimestamp, long finishTimestamp, String version) implements Serializable { + public static final String DATA_FILENAME = "textile_status.data"; + + public Optional validate(long hash, RestoreContext ctx) throws RuntimeException { + if(hash != treeHash) + return Optional.of("Tree Hash mismatch!\n Expected: " + hex(treeHash) + ", got: " + hex(hash)); + + if(!brokenFiles.isEmpty()) return Optional.of("Damaged files present! ^"); + + if(ctx.restoreableFile().getCreationTime().equals(date)) + return Optional.of( + "Creation date mismatch!\n Expected: " + + date.format(DateTimeFormatter.ISO_DATE_TIME) + ", got: " + + ctx.restoreableFile().getCreationTime().format(DateTimeFormatter.ISO_DATE_TIME) + ); + + return Optional.empty(); + } + + public static Path resolveStatusFilename(Path directory) { return directory.resolve(DATA_FILENAME); } + + public static CompressionStatus readFromFile(Path directory) throws IOException, ClassNotFoundException { + try(InputStream i = Files.newInputStream(directory.resolve(DATA_FILENAME)); + ObjectInputStream obj = new ObjectInputStream(i)) { + return (CompressionStatus) obj.readObject(); + } + } + + public byte[] serialize() throws IOException { + try (ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream o = new ObjectOutputStream(bo)) { + o.writeObject(this); + return bo.toByteArray(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("{ "); + builder.append("Hash: ") + .append(hex(treeHash)) + .append(", Date: ") + .append(date.format(DateTimeFormatter.ISO_DATE_TIME)) + .append(", Start timestamp: ").append(startTimestamp) + .append(", Finish timestamp: ").append(finishTimestamp) + .append(", Mod Version: ").append(version); + + builder.append(", Broken files: "); + if(brokenFiles.isEmpty()) builder.append("[]"); + else { + builder.append("[\n"); + for(Path i: brokenFiles.keySet()) { + builder.append(i.toString()) + .append(":"); + + ByteArrayOutputStream o = new ByteArrayOutputStream(); + brokenFiles.get(i).printStackTrace(new PrintStream(o)); + builder.append(o).append("\n"); + } + builder.append("]"); + } + + builder.append(" }"); + + return builder.toString(); + } + + private static String hex(long val) { return "0x" + Long.toHexString(val).toUpperCase(); } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/LivingServer.java b/src/main/java/net/szum123321/textile_backup/core/DataLeftException.java similarity index 77% rename from src/main/java/net/szum123321/textile_backup/core/LivingServer.java rename to src/main/java/net/szum123321/textile_backup/core/DataLeftException.java index 0011d6b..02ac7f0 100644 --- a/src/main/java/net/szum123321/textile_backup/core/LivingServer.java +++ b/src/main/java/net/szum123321/textile_backup/core/DataLeftException.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 @@ -18,6 +18,8 @@ package net.szum123321.textile_backup.core; -public interface LivingServer { - boolean isAlive(); +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/NoSpaceLeftOnDeviceException.java b/src/main/java/net/szum123321/textile_backup/core/NoSpaceLeftOnDeviceException.java index 3f71e4b..a487f89 100644 --- a/src/main/java/net/szum123321/textile_backup/core/NoSpaceLeftOnDeviceException.java +++ b/src/main/java/net/szum123321/textile_backup/core/NoSpaceLeftOnDeviceException.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 @@ -25,6 +25,6 @@ */ public class NoSpaceLeftOnDeviceException extends IOException { public NoSpaceLeftOnDeviceException(Throwable cause) { - super(cause); + super("The underlying filesystem has ran out of available space.\nSee: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems", cause); } } diff --git a/src/main/java/net/szum123321/textile_backup/core/RestoreableFile.java b/src/main/java/net/szum123321/textile_backup/core/RestoreableFile.java index b44d9e5..5db2d24 100644 --- a/src/main/java/net/szum123321/textile_backup/core/RestoreableFile.java +++ b/src/main/java/net/szum123321/textile_backup/core/RestoreableFile.java @@ -1,20 +1,19 @@ /* - * A simple backup mod for Fabric - * Copyright (C) 2022 Szum123321 + * 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 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 . + * 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; @@ -57,7 +56,7 @@ private RestoreableFile(Path file, ConfigPOJO.ArchiveFormat archiveFormat, Local } //removes repetition of the files stream thingy with awfully large lambdas - public static T applyOnFiles(Path root, T def, Consumer errorConsumer, Function, T> streamConsumer) { + public static T applyOnFiles(Path root, T def, Consumer errorConsumer, Function, T> streamConsumer) { try (Stream stream = Files.list(root)) { return streamConsumer.apply(stream.flatMap(f -> RestoreableFile.build(f).stream())); } catch (IOException e) { diff --git a/src/main/java/net/szum123321/textile_backup/core/Utilities.java b/src/main/java/net/szum123321/textile_backup/core/Utilities.java index 65a6f9f..7d82400 100644 --- a/src/main/java/net/szum123321/textile_backup/core/Utilities.java +++ b/src/main/java/net/szum123321/textile_backup/core/Utilities.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; @@ -115,9 +115,9 @@ public static Path getBackupRootPath(String worldName) { } public static boolean isBlacklisted(Path path) { - if(isWindows()) { //hotfix! - if (path.getFileName().toString().equals("session.lock")) return true; - } + if (path.getFileName().equals("session.lock")) return true; + + if(path.getFileName().endsWith(CompressionStatus.DATA_FILENAME)) return true; return config.get().fileBlacklist.stream().anyMatch(path::startsWith); } diff --git a/src/main/java/net/szum123321/textile_backup/core/create/BackupContext.java b/src/main/java/net/szum123321/textile_backup/core/create/BackupContext.java deleted file mode 100644 index 63a3e3e..0000000 --- a/src/main/java/net/szum123321/textile_backup/core/create/BackupContext.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 . - */ - -package net.szum123321.textile_backup.core.create; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.ServerCommandSource; -import net.szum123321.textile_backup.core.ActionInitiator; -import org.jetbrains.annotations.NotNull; - -public record BackupContext(@NotNull MinecraftServer server, - ServerCommandSource commandSource, - ActionInitiator initiator, - boolean save, - String comment) { - - public boolean startedByPlayer() { - return initiator == ActionInitiator.Player; - } - - public boolean shouldSave() { - return save; - } - - public static class Builder { - private MinecraftServer server; - private ServerCommandSource commandSource; - private ActionInitiator initiator; - private boolean save; - private String comment; - - private boolean guessInitiator; - - public Builder() { - this.server = null; - this.commandSource = null; - this.initiator = null; - this.save = false; - this.comment = null; - - guessInitiator = false; - } - - public static Builder newBackupContextBuilder() { - return new Builder(); - } - - public Builder setCommandSource(ServerCommandSource commandSource) { - this.commandSource = commandSource; - return this; - } - - public Builder setServer(MinecraftServer server) { - this.server = server; - return this; - } - - public Builder setInitiator(ActionInitiator initiator) { - this.initiator = initiator; - return this; - } - - public Builder setComment(String comment) { - this.comment = comment; - return this; - } - - public Builder guessInitiator() { - this.guessInitiator = true; - return this; - } - - public Builder saveServer() { - this.save = true; - return this; - } - - public BackupContext build() { - if (guessInitiator) { - initiator = commandSource.getEntity() instanceof PlayerEntity ? ActionInitiator.Player : ActionInitiator.ServerConsole; - } else if (initiator == null) { - initiator = ActionInitiator.Null; - } - - if (server == null) { - if (commandSource != null) setServer(commandSource.getServer()); - else throw new RuntimeException("Neither MinecraftServer or ServerCommandSource were provided!"); - } - - return new BackupContext(server, commandSource, initiator, save, comment); - } - } -} diff --git a/src/main/java/net/szum123321/textile_backup/core/create/BackupScheduler.java b/src/main/java/net/szum123321/textile_backup/core/create/BackupScheduler.java index fb54d37..55a13f3 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/BackupScheduler.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/BackupScheduler.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 @@ -53,14 +53,13 @@ public static void tick(MinecraftServer server) { if(nextBackup <= now) { //It's time to run Globals.INSTANCE.getQueueExecutor().submit( - MakeBackupRunnableFactory.create( - BackupContext.Builder - .newBackupContextBuilder() - .setServer(server) - .setInitiator(ActionInitiator.Timer) - .saveServer() - .build() - ) + ExecutableBackup.Builder + .newBackupContextBuilder() + .setServer(server) + .setInitiator(ActionInitiator.Timer) + .saveServer() + .announce() + .build() ); nextBackup = now + config.get().backupInterval; @@ -76,14 +75,13 @@ public static void tick(MinecraftServer server) { if(scheduled && nextBackup <= now) { //Verify we hadn't done the final one, and it's time to do so Globals.INSTANCE.getQueueExecutor().submit( - MakeBackupRunnableFactory.create( - BackupContext.Builder - .newBackupContextBuilder() - .setServer(server) - .setInitiator(ActionInitiator.Timer) - .saveServer() - .build() - ) + ExecutableBackup.Builder + .newBackupContextBuilder() + .setServer(server) + .setInitiator(ActionInitiator.Timer) + .saveServer() + .announce() + .build() ); scheduled = false; 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..8e06585 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/create/BrokenFileHandler.java @@ -0,0 +1,34 @@ +/* + * 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 java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class BrokenFileHandler { + private final Map store = new HashMap<>(); + 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/ExecutableBackup.java b/src/main/java/net/szum123321/textile_backup/core/create/ExecutableBackup.java new file mode 100644 index 0000000..f51e033 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/create/ExecutableBackup.java @@ -0,0 +1,231 @@ +package net.szum123321.textile_backup.core.create; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.szum123321.textile_backup.Globals; +import net.szum123321.textile_backup.TextileBackup; +import net.szum123321.textile_backup.TextileLogger; +import net.szum123321.textile_backup.config.ConfigHelper; +import net.szum123321.textile_backup.core.ActionInitiator; +import net.szum123321.textile_backup.core.Cleanup; +import net.szum123321.textile_backup.core.Utilities; +import net.szum123321.textile_backup.core.create.compressors.ParallelZipCompressor; +import net.szum123321.textile_backup.core.create.compressors.ZipCompressor; +import net.szum123321.textile_backup.core.create.compressors.tar.AbstractTarArchiver; +import net.szum123321.textile_backup.core.create.compressors.tar.ParallelGzipCompressor; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.NoSuchElementException; +import java.util.concurrent.Callable; + +public record ExecutableBackup(@NotNull MinecraftServer server, + ServerCommandSource commandSource, + ActionInitiator initiator, + boolean save, + boolean cleanup, + String comment, + LocalDateTime startDate) implements Callable { + + private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); + private final static ConfigHelper config = ConfigHelper.INSTANCE; + + public boolean startedByPlayer() { + return initiator == ActionInitiator.Player; + } + + public void announce() { + if(config.get().broadcastBackupStart) { + Utilities.notifyPlayers(server, + "Warning! Server backup will begin shortly. You may experience some lag." + ); + } else { + log.sendInfoAL(this, "Warning! Server backup will begin shortly. You may experience some lag."); + } + + StringBuilder builder = new StringBuilder(); + + builder.append("Backup started "); + + builder.append(initiator.getPrefix()); + + if(startedByPlayer()) + builder.append(commandSource.getDisplayName().getString()); + else + builder.append(initiator.getName()); + + builder.append(" on: "); + builder.append(Utilities.getDateTimeFormatter().format(LocalDateTime.now())); + + log.info(builder.toString()); + } + @Override + public Void call() throws Exception { + if (save) { //save the world + log.sendInfoAL(this, "Saving server..."); + server.saveAll(true, true, false); + } + + Path outFile = Utilities.getBackupRootPath(Utilities.getLevelName(server)).resolve(getFileName()); + + log.trace("Outfile is: {}", outFile); + + try { + //I think I should synchronise these two next calls... + Utilities.disableWorldSaving(server); + Globals.INSTANCE.disableWatchdog = true; + + Globals.INSTANCE.updateTMPFSFlag(server); + + log.sendInfoAL(this, "Starting backup"); + + Path world = Utilities.getWorldFolder(server); + + log.trace("Minecraft world is: {}", world); + + Files.createDirectories(outFile.getParent()); + Files.createFile(outFile); + + int coreCount; + + if (config.get().compressionCoreCountLimit <= 0) coreCount = Runtime.getRuntime().availableProcessors(); + else + coreCount = Math.min(config.get().compressionCoreCountLimit, Runtime.getRuntime().availableProcessors()); + + log.trace("Running compression on {} threads. Available cores: {}", coreCount, Runtime.getRuntime().availableProcessors()); + + switch (config.get().format) { + case ZIP -> { + if (coreCount > 1 && !Globals.INSTANCE.disableTMPFS()) { + log.trace("Using PARALLEL Zip Compressor. Threads: {}", coreCount); + ParallelZipCompressor.getInstance().createArchive(world, outFile, this, coreCount); + } else { + log.trace("Using REGULAR Zip Compressor."); + ZipCompressor.getInstance().createArchive(world, outFile, this, coreCount); + } + } + case GZIP -> ParallelGzipCompressor.getInstance().createArchive(world, outFile, this, coreCount); + case TAR -> new AbstractTarArchiver().createArchive(world, outFile, this, coreCount); + } + + if(cleanup) new Cleanup(commandSource, Utilities.getLevelName(server)).call(); + + if (config.get().broadcastBackupDone) Utilities.notifyPlayers(server, "Done!"); + else log.sendInfoAL(this, "Done!"); + + } catch (Throwable e) { + //ExecutorService swallows exception, so I need to catch everything + log.error("An exception occurred when trying to create a new backup file!", e); + + if (ConfigHelper.INSTANCE.get().integrityVerificationMode.isStrict()) { + try { + Files.delete(outFile); + } catch (IOException ex) { + log.error("An exception occurred while trying go delete: {}", outFile, ex); + } + } + + if (initiator == ActionInitiator.Player) + log.sendError(this, "An exception occurred when trying to create new backup file!"); + + throw e; + } finally { + Utilities.enableWorldSaving(server); + Globals.INSTANCE.disableWatchdog = false; + } + + return null; + } + + private String getFileName() { + return Utilities.getDateTimeFormatter().format(startDate) + + (comment != null ? "#" + comment.replaceAll("[\\\\/:*?\"<>|#]", "") : "") + + config.get().format.getCompleteString(); + } + public static class Builder { + private MinecraftServer server; + private ServerCommandSource commandSource; + private ActionInitiator initiator; + private boolean save; + private boolean cleanup; + private String comment; + private boolean announce; + + private boolean guessInitiator; + + public Builder() { + this.server = null; + this.commandSource = null; + this.initiator = null; + this.save = false; + cleanup = true; //defaults + this.comment = null; + this.announce = false; + + guessInitiator = false; + } + + public static ExecutableBackup.Builder newBackupContextBuilder() { + return new ExecutableBackup.Builder(); + } + + public ExecutableBackup.Builder setCommandSource(ServerCommandSource commandSource) { + this.commandSource = commandSource; + return this; + } + + public ExecutableBackup.Builder setServer(MinecraftServer server) { + this.server = server; + return this; + } + + public ExecutableBackup.Builder setInitiator(ActionInitiator initiator) { + this.initiator = initiator; + return this; + } + + public ExecutableBackup.Builder setComment(String comment) { + this.comment = comment; + return this; + } + + public ExecutableBackup.Builder guessInitiator() { + this.guessInitiator = true; + return this; + } + + public ExecutableBackup.Builder saveServer() { + this.save = true; + return this; + } + + public ExecutableBackup.Builder noCleanup() { + this.cleanup = false; + return this; + } + + public ExecutableBackup.Builder announce() { + this.announce = true; + return this; + } + + public ExecutableBackup build() { + if (guessInitiator) { + initiator = Utilities.wasSentByPlayer(commandSource) ? ActionInitiator.Player : ActionInitiator.ServerConsole; + } else if (initiator == null) throw new NoSuchElementException("No initiator provided!"); + + if (server == null) { + if (commandSource != null) setServer(commandSource.getServer()); + else throw new RuntimeException("Neither MinecraftServer or ServerCommandSource were provided!"); + } + + ExecutableBackup v = new ExecutableBackup(server, commandSource, initiator, save, cleanup, comment, LocalDateTime.now()); + + if(announce) v.announce(); + return v; + } + } +} 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 new file mode 100644 index 0000000..2c43a25 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/create/FileInputStreamSupplier.java @@ -0,0 +1,70 @@ +/* + * 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.digest.FileTreeHashBuilder; +import net.szum123321.textile_backup.core.digest.HashingInputStream; + +import java.io.IOException; +import java.io.InputStream; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +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, hashTreeBuilder, brokenFileHandler); + } catch (IOException e) { + //Probably good idea to just put it here. In the case an exception is thrown here, it could be possible + //The latch would have never been lifted + hashTreeBuilder.update(path, 0, 0); + brokenFileHandler.handle(path, e); + throw e; + } + } + + @Override + public Optional getPath() { return Optional.of(path); } + + @Override + public long size() throws IOException { return Files.size(path); } + + @Override + public String getName() { + return name; + } + + @Override + public InputStream get() { + try { + return getInputStream(); + } catch (IOException e) { + log.error("An exception occurred while trying to create an input stream from file: {}!", path.toString(), e); + } + + return null; + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/create/InputSupplier.java b/src/main/java/net/szum123321/textile_backup/core/create/InputSupplier.java new file mode 100644 index 0000000..76a40e7 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/create/InputSupplier.java @@ -0,0 +1,35 @@ +/* + * 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.apache.commons.compress.parallel.InputStreamSupplier; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Optional; + +public interface InputSupplier extends InputStreamSupplier { + InputStream getInputStream() throws IOException; + //If an entry is virtual (a.k.a. there is no actual file to open, only input stream) + Optional getPath(); + String getName(); + + long size() throws IOException; +} 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 deleted file mode 100644 index f2184b9..0000000 --- a/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnable.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - 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 . -*/ - -package net.szum123321.textile_backup.core.create; - -import net.szum123321.textile_backup.Globals; -import net.szum123321.textile_backup.TextileBackup; -import net.szum123321.textile_backup.TextileLogger; -import net.szum123321.textile_backup.config.ConfigHelper; -import net.szum123321.textile_backup.core.ActionInitiator; -import net.szum123321.textile_backup.core.Cleanup; -import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.compressors.ParallelZipCompressor; -import net.szum123321.textile_backup.core.create.compressors.ZipCompressor; -import net.szum123321.textile_backup.core.create.compressors.tar.AbstractTarArchiver; -import net.szum123321.textile_backup.core.create.compressors.tar.ParallelBZip2Compressor; -import net.szum123321.textile_backup.core.create.compressors.tar.ParallelGzipCompressor; -import org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; - -/** - * The actual object responsible for creating the backup - */ -public class MakeBackupRunnable implements Runnable { - private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - private final static ConfigHelper config = ConfigHelper.INSTANCE; - - private final BackupContext context; - - public MakeBackupRunnable(BackupContext context) { - this.context = context; - } - - @Override - public void run() { - try { - Utilities.disableWorldSaving(context.server()); - Globals.INSTANCE.disableWatchdog = true; - - Globals.INSTANCE.updateTMPFSFlag(context.server()); - - log.sendInfoAL(context, "Starting backup"); - - Path world = Utilities.getWorldFolder(context.server()); - - log.trace("Minecraft world is: {}", world); - - Path outFile = Utilities - .getBackupRootPath(Utilities.getLevelName(context.server())) - .resolve(getFileName()); - - log.trace("Outfile is: {}", outFile); - - Files.createDirectories(outFile.getParent()); - Files.createFile(outFile); - - int coreCount; - - if(config.get().compressionCoreCountLimit <= 0) { - coreCount = Runtime.getRuntime().availableProcessors(); - } else { - coreCount = Math.min(config.get().compressionCoreCountLimit, Runtime.getRuntime().availableProcessors()); - } - - log.trace("Running compression on {} threads. Available cores: {}", coreCount, Runtime.getRuntime().availableProcessors()); - - switch (config.get().format) { - case ZIP -> { - if (coreCount > 1 && !Globals.INSTANCE.disableTMPFS()) { - log.trace("Using PARALLEL Zip Compressor. Threads: {}", coreCount); - ParallelZipCompressor.getInstance().createArchive(world, outFile, context, coreCount); - } else { - log.trace("Using REGULAR Zip Compressor."); - ZipCompressor.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 LZMA -> new AbstractTarArchiver() { - protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { - return new LZMACompressorOutputStream(stream); - } - }.createArchive(world, outFile, context, coreCount); - case TAR -> new AbstractTarArchiver().createArchive(world, outFile, context, coreCount); - } - - new Cleanup(context.commandSource(), Utilities.getLevelName(context.server())).call(); - - if(config.get().broadcastBackupDone) { - Utilities.notifyPlayers( - context.server(), - "Done!" - ); - } else { - log.sendInfoAL(context, "Done!"); - } - } catch (Throwable e) { - //ExecutorService swallows exception, so I need to catch everything - log.error("An exception occurred when trying to create new backup file!", e); - - if(context.initiator() == ActionInitiator.Player) - log.sendError(context, "An exception occurred when trying to create new backup file!"); - } finally { - Utilities.enableWorldSaving(context.server()); - Globals.INSTANCE.disableWatchdog = false; - } - } - - private String getFileName(){ - LocalDateTime now = LocalDateTime.now(); - - return Utilities.getDateTimeFormatter().format(now) + - (context.comment() != null ? "#" + context.comment().replaceAll("[\\\\/:*?\"<>|#]", "") : "") + - config.get().format.getCompleteString(); - } -} diff --git a/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnableFactory.java b/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnableFactory.java deleted file mode 100644 index 36d8eb6..0000000 --- a/src/main/java/net/szum123321/textile_backup/core/create/MakeBackupRunnableFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.config.ConfigHelper; -import net.szum123321.textile_backup.core.Utilities; - -import java.time.LocalDateTime; - -public class MakeBackupRunnableFactory { - private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - private final static ConfigHelper config = ConfigHelper.INSTANCE; - - public static Runnable create(BackupContext ctx) { - if(config.get().broadcastBackupStart) { - Utilities.notifyPlayers(ctx.server(), - "Warning! Server backup will begin shortly. You may experience some lag." - ); - } else { - log.sendInfoAL(ctx, "Warning! Server backup will begin shortly. You may experience some lag."); - } - - StringBuilder builder = new StringBuilder(); - - builder.append("Backup started "); - - builder.append(ctx.initiator().getPrefix()); - - if(ctx.startedByPlayer()) - builder.append(ctx.commandSource().getDisplayName().getString()); - else - builder.append(ctx.initiator().getName()); - - builder.append(" on: "); - builder.append(Utilities.getDateTimeFormatter().format(LocalDateTime.now())); - - log.info(builder.toString()); - - if (ctx.shouldSave()) { - log.sendInfoAL(ctx, "Saving server..."); - - ctx.server().getPlayerManager().saveAllPlayerData(); - - try { - ctx.server().save(false, true, true); - } catch (Exception e) { - log.sendErrorAL(ctx,"An exception occurred when trying to save the world!"); - } - } - - return new MakeBackupRunnable(ctx); - } -} 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 45e5f65..01d3de1 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 @@ -18,77 +18,96 @@ package net.szum123321.textile_backup.core.create.compressors; +import net.szum123321.textile_backup.Globals; 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.NoSpaceLeftOnDeviceException; -import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.config.ConfigHelper; +import net.szum123321.textile_backup.core.*; +import net.szum123321.textile_backup.core.create.BrokenFileHandler; +import net.szum123321.textile_backup.core.create.ExecutableBackup; +import net.szum123321.textile_backup.core.create.FileInputStreamSupplier; +import net.szum123321.textile_backup.core.create.InputSupplier; +import net.szum123321.textile_backup.core.digest.FileTreeHashBuilder; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.stream.Stream; /** - * Basic abstract class representing directory compressor + * Basic abstract class representing directory compressor with all the bells and whistles */ public abstract class AbstractCompressor { private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - public void createArchive(Path inputFile, Path outputFile, BackupContext ctx, int coreLimit) { + public void createArchive(Path inputFile, Path outputFile, ExecutableBackup ctx, int coreLimit) throws IOException, ExecutionException, InterruptedException { Instant start = Instant.now(); + BrokenFileHandler brokenFileHandler = new BrokenFileHandler(); //Basically a hashmap storing files and their respective exceptions + try (OutputStream outStream = Files.newOutputStream(outputFile); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outStream); OutputStream arc = createArchiveOutputStream(bufferedOutputStream, ctx, coreLimit); Stream fileStream = Files.walk(inputFile)) { - fileStream + var fileList = fileStream .filter(path -> !Utilities.isBlacklisted(inputFile.relativize(path))) - .filter(Files::isRegularFile).forEach(file -> { - try { - //hopefully one broken file won't spoil the whole archive - addEntry(file, inputFile.relativize(file).toString(), arc); - } catch (IOException e) { - log.error("An exception occurred while trying to compress: {}", inputFile.relativize(file).toString(), e); - - if (ctx.initiator() == ActionInitiator.Player) - log.sendError(ctx, "Something went wrong while compressing files!"); - } - }); + .filter(Files::isRegularFile) + .toList(); - finish(arc); - } catch(NoSpaceLeftOnDeviceException e) { - log.error(""" - CRITICAL ERROR OCCURRED! - The backup is corrupt! - Don't panic! This is a known issue! - 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); - - if(ctx.initiator() == ActionInitiator.Player) { - log.sendError(ctx, "Backup failed. The file is corrupt."); - log.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems"); + FileTreeHashBuilder fileHashBuilder = new FileTreeHashBuilder(fileList.size()); + + for (Path file : fileList) { + try { + addEntry( + new FileInputStreamSupplier( + file, + inputFile.relativize(file).toString(), + fileHashBuilder, + brokenFileHandler), + arc + ); + } catch (IOException e) { + brokenFileHandler.handle(file, e); + fileHashBuilder.update(file, 0, 0); + //In Permissive mode we allow partial backups + if (ConfigHelper.INSTANCE.get().integrityVerificationMode.isStrict()) throw e; + else log.sendErrorAL(ctx, "An exception occurred while trying to compress: {}", + inputFile.relativize(file).toString(), e + ); + } } - } 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!"); + + arc.flush(); + + //wait for all the InputStreams to close/fail with InputSupplier + + Instant now = Instant.now(); + + long treeHash = fileHashBuilder.getValue(true); + CompressionStatus status = new CompressionStatus ( + treeHash, + brokenFileHandler.get(), + ctx.startDate(), start.toEpochMilli(), now.toEpochMilli(), + Globals.INSTANCE.getCombinedVersionString() + ); + + addEntry(new StatusFileInputSupplier(status.serialize()), arc); + + finish(arc); } finally { close(); } - // close(); - log.sendInfoAL(ctx, "Compression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); } - protected abstract OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException; - protected abstract void addEntry(Path file, String entryName, OutputStream arc) throws IOException; + protected abstract OutputStream createArchiveOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) throws IOException; + protected abstract void addEntry(InputSupplier inputSupplier, OutputStream arc) throws IOException; protected void finish(OutputStream arc) throws InterruptedException, ExecutionException, IOException { //This function is only needed for the ParallelZipCompressor to write out ParallelScatterZipCreator @@ -97,4 +116,16 @@ protected void finish(OutputStream arc) throws InterruptedException, ExecutionEx protected void close() { //Same as above, just for ParallelGzipCompressor to shut down ExecutorService } -} + + private record StatusFileInputSupplier(byte[] data) implements InputSupplier { + public InputStream getInputStream() { return new ByteArrayInputStream(data); } + + public Optional getPath() { return Optional.empty(); } + + public String getName() { return CompressionStatus.DATA_FILENAME; } + + public long size() { return data.length; } + + 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 7933067..7ed67a1 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 @@ -21,9 +21,9 @@ import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; import net.szum123321.textile_backup.core.NoSpaceLeftOnDeviceException; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; +import net.szum123321.textile_backup.core.create.InputSupplier; import org.apache.commons.compress.archivers.zip.*; -import org.apache.commons.compress.parallel.InputStreamSupplier; import java.io.*; import java.nio.file.Files; @@ -61,25 +61,32 @@ public static ParallelZipCompressor getInstance() { } @Override - protected OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) { + protected OutputStream createArchiveOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) { scatterZipCreator = new ParallelScatterZipCreator(Executors.newFixedThreadPool(coreLimit)); return super.createArchiveOutputStream(stream, ctx, coreLimit); } @Override - protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException { - ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName); - - if(ZipCompressor.isDotDat(file.getFileName().toString())) { + protected void addEntry(InputSupplier input, OutputStream arc) throws IOException { + ZipArchiveEntry entry; + if(input.getPath().isEmpty()) { + entry = new ZipArchiveEntry(input.getName()); entry.setMethod(ZipEntry.STORED); - entry.setSize(Files.size(file)); - entry.setCompressedSize(Files.size(file)); - entry.setCrc(getCRC(file)); - } else entry.setMethod(ZipEntry.DEFLATED); + entry.setSize(input.size()); + } else { + Path file = input.getPath().get(); + entry = (ZipArchiveEntry) ((ZipArchiveOutputStream) arc).createArchiveEntry(file, input.getName()); + if (ZipCompressor.isDotDat(file.toString())) { + entry.setMethod(ZipEntry.STORED); + entry.setSize(Files.size(file)); + entry.setCompressedSize(Files.size(file)); + entry.setCrc(getCRC(file)); + } else entry.setMethod(ZipEntry.DEFLATED); + } entry.setTime(System.currentTimeMillis()); - scatterZipCreator.addArchiveEntry(entry, new FileInputStreamSupplier(file)); + scatterZipCreator.addArchiveEntry(entry, input); } @Override @@ -97,10 +104,9 @@ This line causes the infamous Out of space error (#20 and #80) boolean match = (cause.getStackTrace().length >= STACKTRACE_NO_SPACE_ON_LEFT_ON_DEVICE.length); if(match) { for(int i = 0; i < STACKTRACE_NO_SPACE_ON_LEFT_ON_DEVICE.length && match; i++) - if(!STACKTRACE_NO_SPACE_ON_LEFT_ON_DEVICE[i].equals(cause.getStackTrace()[i])) match = false; - + if(!STACKTRACE_NO_SPACE_ON_LEFT_ON_DEVICE[i].matches(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); } } @@ -114,29 +120,8 @@ private record SimpleStackTraceElement ( String methodName, boolean isNative ) { - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - if(o.getClass() == StackTraceElement.class) { - StackTraceElement that = (StackTraceElement) o; - return (isNative == that.isNativeMethod()) && Objects.equals(className, that.getClassName()) && Objects.equals(methodName, that.getMethodName()); - } - if(getClass() != o.getClass()) return false; - SimpleStackTraceElement that = (SimpleStackTraceElement) o; - return isNative == that.isNative && Objects.equals(className, that.className) && Objects.equals(methodName, that.methodName); - } - } - - record FileInputStreamSupplier(Path sourceFile) implements InputStreamSupplier { - public InputStream get() { - try { - return Files.newInputStream(sourceFile); - } catch (IOException e) { - log.error("An exception occurred while trying to create an input stream from file: {}!", sourceFile.toString(), e); - } - - return null; + public boolean matches(StackTraceElement o) { + return (isNative == o.isNativeMethod()) && Objects.equals(className, o.getClassName()) && Objects.equals(methodName, o.getMethodName()); } } } diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/ZipCompressor.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/ZipCompressor.java index 0210708..a1b224b 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/ZipCompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/ZipCompressor.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,7 +20,8 @@ import net.szum123321.textile_backup.config.ConfigHelper; import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; +import net.szum123321.textile_backup.core.create.InputSupplier; import org.apache.commons.compress.archivers.zip.Zip64Mode; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; @@ -42,7 +43,7 @@ public static ZipCompressor getInstance() { } @Override - protected OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) { + protected OutputStream createArchiveOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) { ZipArchiveOutputStream arc = new ZipArchiveOutputStream(stream); arc.setMethod(ZipArchiveOutputStream.DEFLATED); @@ -54,15 +55,23 @@ protected OutputStream createArchiveOutputStream(OutputStream stream, BackupCont } @Override - protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException { - try (InputStream fileInputStream = Files.newInputStream(file)){ - ZipArchiveEntry entry = (ZipArchiveEntry)((ZipArchiveOutputStream)arc).createArchiveEntry(file, entryName); + protected void addEntry(InputSupplier input, OutputStream arc) throws IOException { + try (InputStream fileInputStream = input.getInputStream()) { + ZipArchiveEntry entry; - if(isDotDat(file.getFileName().toString())) { + if(input.getPath().isEmpty()) { + entry = new ZipArchiveEntry(input.getName()); entry.setMethod(ZipEntry.STORED); - entry.setSize(Files.size(file)); - entry.setCompressedSize(Files.size(file)); - entry.setCrc(getCRC(file)); + entry.setSize(input.size()); + } else { + Path file = input.getPath().get(); + entry = (ZipArchiveEntry) ((ZipArchiveOutputStream) arc).createArchiveEntry(file, input.getName()); + if (isDotDat(file.toString())) { + entry.setMethod(ZipEntry.STORED); + entry.setSize(Files.size(file)); + entry.setCompressedSize(Files.size(file)); + entry.setCrc(getCRC(file)); + } else entry.setMethod(ZipEntry.DEFLATED); } ((ZipArchiveOutputStream)arc).putArchiveEntry(entry); diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/AbstractTarArchiver.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/AbstractTarArchiver.java index 04489b4..52e86e5 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/AbstractTarArchiver.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/AbstractTarArchiver.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 @@ -18,23 +18,22 @@ package net.szum123321.textile_backup.core.create.compressors.tar; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import net.szum123321.textile_backup.core.create.compressors.AbstractCompressor; +import net.szum123321.textile_backup.core.create.InputSupplier; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.utils.IOUtils; import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; public class AbstractTarArchiver extends AbstractCompressor { - protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { + protected OutputStream getCompressorOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) throws IOException { return stream; } @Override - protected OutputStream createArchiveOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { + protected OutputStream createArchiveOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) throws IOException { TarArchiveOutputStream tar = new TarArchiveOutputStream(getCompressorOutputStream(stream, ctx, coreLimit)); tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX); @@ -43,9 +42,15 @@ protected OutputStream createArchiveOutputStream(OutputStream stream, BackupCont } @Override - protected void addEntry(Path file, String entryName, OutputStream arc) throws IOException { - try (InputStream fileInputStream = Files.newInputStream(file)){ - TarArchiveEntry entry = (TarArchiveEntry)((TarArchiveOutputStream) arc).createArchiveEntry(file, entryName); + protected void addEntry(InputSupplier input, OutputStream arc) throws IOException { + try (InputStream fileInputStream = input.getInputStream()) { + TarArchiveEntry entry; + if(input.getPath().isEmpty()) { //Virtual entry + entry = new TarArchiveEntry(input.getName()); + entry.setSize(input.size()); + } else + entry = (TarArchiveEntry)((TarArchiveOutputStream) arc).createArchiveEntry(input.getPath().get(), input.getName()); + ((TarArchiveOutputStream)arc).putArchiveEntry(entry); IOUtils.copy(fileInputStream, arc); diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelBZip2Compressor.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelBZip2Compressor.java index e3e9e05..2bd2840 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelBZip2Compressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelBZip2Compressor.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 @@ -18,7 +18,7 @@ package net.szum123321.textile_backup.core.create.compressors.tar; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import org.at4j.comp.bzip2.BZip2OutputStream; import org.at4j.comp.bzip2.BZip2OutputStreamSettings; @@ -30,7 +30,7 @@ public static ParallelBZip2Compressor getInstance() { } @Override - protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { + protected OutputStream getCompressorOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) throws IOException { return new BZip2OutputStream(stream, new BZip2OutputStreamSettings().setNumberOfEncoderThreads(coreLimit)); } } \ No newline at end of file diff --git a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelGzipCompressor.java b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelGzipCompressor.java index dcf9f1c..94b9da6 100644 --- a/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelGzipCompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/create/compressors/tar/ParallelGzipCompressor.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 @@ -18,7 +18,7 @@ package net.szum123321.textile_backup.core.create.compressors.tar; -import net.szum123321.textile_backup.core.create.BackupContext; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import org.anarres.parallelgzip.ParallelGZIPOutputStream; import java.io.*; @@ -33,7 +33,7 @@ public static ParallelGzipCompressor getInstance() { } @Override - protected OutputStream getCompressorOutputStream(OutputStream stream, BackupContext ctx, int coreLimit) throws IOException { + protected OutputStream getCompressorOutputStream(OutputStream stream, ExecutableBackup ctx, int coreLimit) throws IOException { executorService = Executors.newFixedThreadPool(coreLimit); return new ParallelGZIPOutputStream(stream, executorService); diff --git a/src/main/java/net/szum123321/textile_backup/core/digest/BalticHash.java b/src/main/java/net/szum123321/textile_backup/core/digest/BalticHash.java new file mode 100644 index 0000000..14a6050 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/digest/BalticHash.java @@ -0,0 +1,100 @@ +/* + * 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.digest; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * This algorithm copies the construction of SeaHash including its IV. + * What it differs in is that it uses Xoroshift64* instead of PCG as its pseudo-random function. Although it might lower + * the output quality, I don't think it matters that much, honestly. One advantage of xoroshift is that it should be + * easier to implement with AVX. Java should soon ship its vector api by default. + */ +public class BalticHash implements Hash { + //SeaHash IV + protected final static long[] IV = { 0x16f11fe89b0d677cL, 0xb480a793d8e6c86cL, 0x6fe2e5aaf078ebc9L, 0x14f994a4c5259381L }; + private final long[] state = Arrays.copyOf(IV, IV.length); + protected final int buffer_limit = state.length * Long.BYTES; + protected final byte[] _byte_buffer = new byte[(state.length + 1) * Long.BYTES]; + //Enforce endianness + protected final ByteBuffer buffer = ByteBuffer.wrap(_byte_buffer).order(ByteOrder.LITTLE_ENDIAN); + protected long hashed_data_length = 0; + + public void update(int b) { + buffer.put((byte)b); + hashed_data_length += 1; + if (buffer.position() >= buffer_limit) round(); + } + + public void update(long b) { + buffer.putLong(b); + hashed_data_length += Long.BYTES; + if(buffer.position() >= buffer_limit) round(); + } + + public void update(byte[] data, int off, int len) { + int pos = 0; + while(pos < len) { + int n = Math.min(len - pos, buffer_limit - buffer.position()); + System.arraycopy(data, off + pos, _byte_buffer, buffer.position(), n); + pos += n; + buffer.position(buffer.position() + n); + if(buffer.position() >= buffer_limit) round(); + } + + hashed_data_length += len; + } + + public long getValue() { + if(buffer.position() != 0) { + while(buffer.position() < buffer_limit) buffer.put((byte)0); + round(); + } + + long result = state[0]; + result ^= state[1]; + result ^= state[2]; + result ^= state[3]; + result ^= hashed_data_length; + + return xorshift64star(result); + } + + protected void round() { + int p = buffer.position(); + buffer.rewind(); + + for(int i = 0; i < 4; i++) state[i] ^= buffer.getLong(); + for(int i = 0; i < 4; i++) state[i] = xorshift64star(state[i]); + + if(p > buffer_limit) { + System.arraycopy(_byte_buffer, buffer_limit, _byte_buffer, 0, buffer.limit() - p); + buffer.position(buffer.limit() - p); + } else buffer.rewind(); + } + + long xorshift64star(long s) { + s ^= (s >> 12); + s ^= (s << 25); + s ^= (s >> 27); + return s * 0x2545F4914F6CDD1DL; + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/digest/FileTreeHashBuilder.java b/src/main/java/net/szum123321/textile_backup/core/digest/FileTreeHashBuilder.java new file mode 100644 index 0000000..64c03b4 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/digest/FileTreeHashBuilder.java @@ -0,0 +1,73 @@ +/* + * 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.digest; + +import net.szum123321.textile_backup.Globals; +import net.szum123321.textile_backup.TextileBackup; +import net.szum123321.textile_backup.TextileLogger; +import net.szum123321.textile_backup.core.CompressionStatus; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; + +/** + * What this class does is it collects the hashed files and combines them into a single number, + * thus we can verify file tree integrity + */ +public class FileTreeHashBuilder { + private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); + private final Object lock = new Object(); + private long hash = 0, filesProcessed = 0, filesTotalSize = 0; + + private final CountDownLatch latch; + + public FileTreeHashBuilder(int filesToProcess) { + latch = new CountDownLatch(filesToProcess); + } + + public void update(Path path, long newHash, long bytes) throws IOException { + if(path.getFileName().toString().equals(CompressionStatus.DATA_FILENAME)) return; + + latch.countDown(); + + synchronized (lock) { + this.hash ^= newHash; + filesTotalSize += bytes; + filesProcessed++; + } + } + + public int getRemaining() { return (int) latch.getCount(); } + + public long getValue(boolean lock) throws InterruptedException { + long leftover = latch.getCount(); + if(lock) latch.await(); + else if(leftover != 0) log.warn("Finishing with {} files unprocessed!", leftover); + + var hasher = Globals.CHECKSUM_SUPPLIER.get(); + + log.debug("Closing: files: {}, bytes {}, raw hash {}", filesProcessed, filesTotalSize, hash); + hasher.update(hash); + hasher.update(filesProcessed); + hasher.update(filesTotalSize); + + return hasher.getValue(); + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/digest/Hash.java b/src/main/java/net/szum123321/textile_backup/core/digest/Hash.java new file mode 100644 index 0000000..c601cb8 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/digest/Hash.java @@ -0,0 +1,32 @@ +/* + * 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.digest; + +public interface Hash { + + void update(int b); + + void update(long b); + + default void update(byte[] b) { update(b, 0, b.length); } + + void update(byte[] b, int off, int len); + + long getValue(); +} diff --git a/src/main/java/net/szum123321/textile_backup/core/digest/HashingInputStream.java b/src/main/java/net/szum123321/textile_backup/core/digest/HashingInputStream.java new file mode 100644 index 0000000..a288b25 --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/digest/HashingInputStream.java @@ -0,0 +1,88 @@ +/* + * 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.digest; + +import net.szum123321.textile_backup.Globals; +import net.szum123321.textile_backup.core.DataLeftException; +import net.szum123321.textile_backup.core.create.BrokenFileHandler; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +/** + * This class calculates a hash of the file on the input stream, submits it to FileTreeHashBuilder. + * In case the underlying stream hasn't been read completely in, puts it into BrokeFileHandler + + * Furthermore, ParallelZip works by putting all the file requests into a queue and then compressing them + * with multiple threads. Thus, we have to make sure that all the files have been read before requesting the final value + * That is what CountDownLatch does + */ +public class HashingInputStream extends FilterInputStream { + private final Path path; + private final Hash hash = Globals.CHECKSUM_SUPPLIER.get(); + private final FileTreeHashBuilder hashBuilder; + private final BrokenFileHandler brokenFileHandler; + + private long bytesWritten = 0; + + public HashingInputStream(InputStream in, Path path, FileTreeHashBuilder hashBuilder, BrokenFileHandler brokenFileHandler) { + super(in); + this.path = path; + this.hashBuilder = hashBuilder; + this.brokenFileHandler = brokenFileHandler; + } + + @Override + public int read(byte @NotNull [] b, int off, int len) throws IOException { + int i = in.read(b, off, len); + if(i != -1) { + hash.update(b, off, i); + bytesWritten += i; + } + return i; + } + + @Override + public int read() throws IOException { + int i = in.read(); + if(i != -1) { + hash.update(i); + bytesWritten++; + } + return i; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void close() throws IOException { + hash.update(path.getFileName().toString().getBytes(StandardCharsets.UTF_8)); + + hashBuilder.update(path, hash.getValue(), bytesWritten); + + if(in.available() != 0) brokenFileHandler.handle(path, new DataLeftException(in.available())); + + super.close(); + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/digest/HashingOutputStream.java b/src/main/java/net/szum123321/textile_backup/core/digest/HashingOutputStream.java new file mode 100644 index 0000000..0cb15ca --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/digest/HashingOutputStream.java @@ -0,0 +1,63 @@ +/* + * 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.digest; + +import net.szum123321.textile_backup.Globals; +import org.jetbrains.annotations.NotNull; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +public class HashingOutputStream extends FilterOutputStream { + private final Path path; + private final Hash hash = Globals.CHECKSUM_SUPPLIER.get(); + private final FileTreeHashBuilder hashBuilder; + + private long bytesWritten = 0; + + public HashingOutputStream(OutputStream out, Path path, FileTreeHashBuilder hashBuilder) { + super(out); + this.path = path; + this.hashBuilder = hashBuilder; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + hash.update(b); + bytesWritten++; + } + + @Override + public void write(byte @NotNull [] b, int off, int len) throws IOException { + out.write(b, off, len); + hash.update(b, off, len); + bytesWritten += len; + } + + @Override + public void close() throws IOException { + hash.update(path.getFileName().toString().getBytes(StandardCharsets.UTF_8)); + hashBuilder.update(path, hash.getValue(), bytesWritten); + super.close(); + } +} diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/AwaitThread.java b/src/main/java/net/szum123321/textile_backup/core/restore/AwaitThread.java index c022092..c728d5e 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/AwaitThread.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/AwaitThread.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 diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreBackupRunnable.java b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreBackupRunnable.java index f2dd22d..b3520fc 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreBackupRunnable.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreBackupRunnable.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.restore; @@ -24,18 +24,22 @@ import net.szum123321.textile_backup.config.ConfigHelper; import net.szum123321.textile_backup.config.ConfigPOJO; import net.szum123321.textile_backup.core.ActionInitiator; -import net.szum123321.textile_backup.core.LivingServer; +import net.szum123321.textile_backup.core.CompressionStatus; import net.szum123321.textile_backup.core.Utilities; -import net.szum123321.textile_backup.core.create.BackupContext; -import net.szum123321.textile_backup.core.create.MakeBackupRunnableFactory; +import net.szum123321.textile_backup.core.create.ExecutableBackup; import net.szum123321.textile_backup.core.restore.decompressors.GenericTarDecompressor; import net.szum123321.textile_backup.core.restore.decompressors.ZipDecompressor; +import net.szum123321.textile_backup.mixin.MinecraftServerSessionAccessor; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.FutureTask; -//TODO: Verify backup's validity? +/** + * This class restores a file provided by RestoreContext. + */ public class RestoreBackupRunnable implements Runnable { private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); private final static ConfigHelper config = ConfigHelper.INSTANCE; @@ -53,45 +57,97 @@ public void run() { log.info("Shutting down server..."); ctx.server().stop(false); - awaitServerShutdown(); - - if(config.get().backupOldWorlds) { - MakeBackupRunnableFactory.create( - BackupContext.Builder - .newBackupContextBuilder() - .setServer(ctx.server()) - .setInitiator(ActionInitiator.Restore) - .setComment("Old_World" + (ctx.comment() != null ? "_" + ctx.comment() : "")) - .build() - ).run(); - } - Path worldFile = Utilities.getWorldFolder(ctx.server()); + Path worldFile = Utilities.getWorldFolder(ctx.server()), + tmp; try { - Path tmp = Files.createTempDirectory( - worldFile.getParent(), - ctx.restoreableFile().getFile().getFileName().toString()); + tmp = Files.createTempDirectory( + ctx.server().getRunDirectory().toPath(), + ctx.restoreableFile().getFile().getFileName().toString() + ); + } catch (IOException e) { + log.error("An exception occurred while unpacking backup", e); + return; + } + //By making a separate thread we can start unpacking an old backup instantly + //Let the server shut down gracefully, and wait for the old world backup to complete + FutureTask waitForShutdown = new FutureTask<>(() -> { + ctx.server().getThread().join(); //wait for server thread to die and save all its state + + if(config.get().backupOldWorlds) { + return ExecutableBackup.Builder + .newBackupContextBuilder() + .setServer(ctx.server()) + .setInitiator(ActionInitiator.Restore) + .noCleanup() + .setComment("Old_World" + (ctx.comment() != null ? "_" + ctx.comment() : "")) + .announce() + .build().call(); + } + return null; + }); + + //run the thread. + new Thread(waitForShutdown, "Server shutdown wait thread").start(); + + try { log.info("Starting decompression..."); + long hash; + if (ctx.restoreableFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP) - ZipDecompressor.decompress(ctx.restoreableFile().getFile(), tmp); + hash = ZipDecompressor.decompress(ctx.restoreableFile().getFile(), tmp); else - GenericTarDecompressor.decompress(ctx.restoreableFile().getFile(), tmp); + hash = GenericTarDecompressor.decompress(ctx.restoreableFile().getFile(), tmp); + + log.info("Waiting for server to fully terminate..."); + + //locks until the backup is finished and the server is dead + waitForShutdown.get(); - log.info("Deleting old world..."); + Optional errorMsg; - Utilities.deleteDirectory(worldFile); - Files.move(tmp, worldFile); + if(Files.notExists(CompressionStatus.resolveStatusFilename(tmp))) { + errorMsg = Optional.of("Status file not found!"); + } else { + CompressionStatus status = CompressionStatus.readFromFile(tmp); - if (config.get().deleteOldBackupAfterRestore) { - log.info("Deleting old backup"); + log.info("Status: {}", status); - Files.delete(ctx.restoreableFile().getFile()); + Files.delete(tmp.resolve(CompressionStatus.DATA_FILENAME)); + + errorMsg = status.validate(hash, ctx); } - } catch (IOException e) { + + if(errorMsg.isEmpty() || !config.get().integrityVerificationMode.verify()) { + if (errorMsg.isEmpty()) log.info("Backup valid. Restoring"); + else log.info("Backup is damaged, but verification is disabled [{}]. Restoring", errorMsg.get()); + + //Disables write lock to override world file + ((MinecraftServerSessionAccessor) ctx.server()).getSession().close(); + + Utilities.deleteDirectory(worldFile); + Files.move(tmp, worldFile); + + if (config.get().deleteOldBackupAfterRestore) { + log.info("Deleting restored backup file"); + Files.delete(ctx.restoreableFile().getFile()); + } + } else { + log.error(errorMsg.get()); + } + + } catch (Exception e) { log.error("An exception occurred while trying to restore a backup!", e); + } finally { + //Regardless of what happened, we should still clean up + if(Files.exists(tmp)) { + try { + Utilities.deleteDirectory(tmp); + } catch (IOException ignored) {} + } } //in case we're playing on client @@ -99,14 +155,4 @@ public void run() { log.info("Done!"); } - - private void awaitServerShutdown() { - while(((LivingServer)ctx.server()).isAlive()) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - log.error("Exception occurred!", e); - } - } - } } \ No newline at end of file diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreContext.java b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreContext.java index f5391a5..e0ac528 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreContext.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreContext.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 diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreHelper.java b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreHelper.java index 075e9fd..a0bf092 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/RestoreHelper.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/RestoreHelper.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.restore; diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/GenericTarDecompressor.java b/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/GenericTarDecompressor.java index de4696c..8d06c34 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/GenericTarDecompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/GenericTarDecompressor.java @@ -1,26 +1,28 @@ /* - 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.restore.decompressors; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; +import net.szum123321.textile_backup.core.digest.FileTreeHashBuilder; import net.szum123321.textile_backup.core.Utilities; +import net.szum123321.textile_backup.core.digest.HashingOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.CompressorException; @@ -36,8 +38,9 @@ public class GenericTarDecompressor { private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - public static void decompress(Path input, Path target) throws IOException { + public static long decompress(Path input, Path target) throws IOException { Instant start = Instant.now(); + FileTreeHashBuilder treeBuilder = new FileTreeHashBuilder(0); try (InputStream fileInputStream = Files.newInputStream(input); InputStream bufferedInputStream = new BufferedInputStream(fileInputStream); @@ -46,10 +49,8 @@ public static void decompress(Path input, Path target) throws IOException { TarArchiveEntry entry; while ((entry = archiveInputStream.getNextTarEntry()) != null) { - if(!archiveInputStream.canReadEntryData(entry)) { - log.error("Something when wrong while trying to decompress {}", entry.getName()); - continue; - } + if(!archiveInputStream.canReadEntryData(entry)) + throw new IOException("Couldn't read archive entry! " + entry.getName()); Path file = target.resolve(entry.getName()); @@ -58,8 +59,8 @@ public static void decompress(Path input, Path target) throws IOException { } else { Files.createDirectories(file.getParent()); try (OutputStream outputStream = Files.newOutputStream(file); - BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) { - IOUtils.copy(archiveInputStream, bufferedOutputStream); + HashingOutputStream out = new HashingOutputStream(outputStream, file, treeBuilder)) { + IOUtils.copy(archiveInputStream, out); } } } @@ -68,6 +69,12 @@ public static void decompress(Path input, Path target) throws IOException { } log.info("Decompression took {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); + + try { + return treeBuilder.getValue(false); + } catch (InterruptedException ignored) { + return 0; + } } private static InputStream getCompressorInputStream(InputStream inputStream) throws CompressorException { diff --git a/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/ZipDecompressor.java b/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/ZipDecompressor.java index 0e12347..996f38b 100644 --- a/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/ZipDecompressor.java +++ b/src/main/java/net/szum123321/textile_backup/core/restore/decompressors/ZipDecompressor.java @@ -1,26 +1,28 @@ /* - 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.restore.decompressors; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.TextileLogger; +import net.szum123321.textile_backup.core.digest.FileTreeHashBuilder; import net.szum123321.textile_backup.core.Utilities; +import net.szum123321.textile_backup.core.digest.HashingOutputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.compress.utils.IOUtils; @@ -35,9 +37,11 @@ public class ZipDecompressor { private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); - public static void decompress(Path inputFile, Path target) throws IOException { + public static long decompress(Path inputFile, Path target) throws IOException { Instant start = Instant.now(); + FileTreeHashBuilder hashBuilder = new FileTreeHashBuilder(0); + try(ZipFile zipFile = new ZipFile(inputFile.toFile())) { for (Iterator it = zipFile.getEntries().asIterator(); it.hasNext(); ) { ZipArchiveEntry entry = it.next(); @@ -48,13 +52,21 @@ public static void decompress(Path inputFile, Path target) throws IOException { } else { Files.createDirectories(file.getParent()); try (OutputStream outputStream = Files.newOutputStream(file); - BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) { - IOUtils.copy(zipFile.getInputStream(entry), bufferedOutputStream); + HashingOutputStream out = new HashingOutputStream(outputStream, file, hashBuilder); + InputStream in = zipFile.getInputStream(entry)) { + + IOUtils.copy(in, out); } } } } log.info("Decompression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now()))); + + try { + return hashBuilder.getValue(false); + } catch (InterruptedException ignored) { + return 0; + } } } diff --git a/src/main/java/net/szum123321/textile_backup/mixin/DedicatedServerWatchdogMixin.java b/src/main/java/net/szum123321/textile_backup/mixin/DedicatedServerWatchdogMixin.java index 415939c..5c15842 100644 --- a/src/main/java/net/szum123321/textile_backup/mixin/DedicatedServerWatchdogMixin.java +++ b/src/main/java/net/szum123321/textile_backup/mixin/DedicatedServerWatchdogMixin.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 diff --git a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java b/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java deleted file mode 100644 index 758ba84..0000000 --- a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 . -*/ - -package net.szum123321.textile_backup.mixin; - -import net.minecraft.server.MinecraftServer; -import net.szum123321.textile_backup.core.LivingServer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MinecraftServer.class) -public class MinecraftServerMixin implements LivingServer { - @Unique - private boolean isAlive = true; - - @Inject(method = "shutdown", at = @At("TAIL")) - public void onFinalWorldSave(CallbackInfo ci) { - isAlive = false; - } - - @Unique - @Override - public boolean isAlive() { - return isAlive; - } -} diff --git a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerSessionAccessor.java b/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerSessionAccessor.java index 91a8696..36f099b 100644 --- a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerSessionAccessor.java +++ b/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerSessionAccessor.java @@ -1,6 +1,6 @@ /* * A simple backup mod for Fabric - * Copyright (C) 2021 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 diff --git a/src/main/java/net/szum123321/textile_backup/test/BalticHashTest.java b/src/main/java/net/szum123321/textile_backup/test/BalticHashTest.java new file mode 100644 index 0000000..55300fd --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/test/BalticHashTest.java @@ -0,0 +1,63 @@ +/* + * 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.test; + +import net.minecraft.util.math.random.Random; +import net.szum123321.textile_backup.TextileBackup; +import net.szum123321.textile_backup.TextileLogger; +import net.szum123321.textile_backup.core.digest.BalticHash; + +public class BalticHashTest { + private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME); + final static int TEST_LEN = 21377; //simple prime + public static void run() throws RuntimeException { + log.info("Running hash test"); + Random r = Random.create(2137); + long x = 0; + + byte[] data = new byte[TEST_LEN]; + + for(int i = 0; i < TEST_LEN; i++) data[i] = (byte)r.nextInt(); + + //Test block mode + for(int i = 0; i < 5*2; i++) x ^= randomHash(data, r); + if(x != 0) throw new RuntimeException("Hash mismatch!"); + + log.info("Test passed"); + } + + static long randomHash(byte[] data, Random r) { + int n = data.length; + + BalticHash h = new BalticHash(); + + int m = r.nextBetween(1, n); + + int nn = n, p = 0; + + for(int i = 0; i < m; i++) { + int k = r.nextBetween(1, nn - (m - i - 1)); + h.update(data, p, k); + p += k; + nn -= k; + } + + return h.getValue(); + } +} 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..39b3aa3 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.integrityVerificationMode": "Verify backup integrity", + "text.autoconfig.textile_backup.option.integrityVerificationMode.@Tooltip": "DO NOT ALTER unless fully aware of consequences", + "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..3b349e6 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -38,9 +38,9 @@ ], "depends": { - "fabricloader": ">=0.14.6", + "fabricloader": ">=0.14.0", "fabric": "*", - "minecraft": ">=1.19.1", + "minecraft": "^1.20-", "cloth-config2": "*", "java": ">=16" }, diff --git a/src/main/resources/textile_backup.mixins.json b/src/main/resources/textile_backup.mixins.json index 82c2d00..6222ec9 100644 --- a/src/main/resources/textile_backup.mixins.json +++ b/src/main/resources/textile_backup.mixins.json @@ -4,7 +4,6 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "DedicatedServerWatchdogMixin", - "MinecraftServerMixin", "MinecraftServerSessionAccessor" ], "client": [ diff --git a/src/test/java/net/szum123321/test/textile_backup/TextileBackupTest.java b/src/test/java/net/szum123321/test/textile_backup/TextileBackupTest.java deleted file mode 100644 index bd646f2..0000000 --- a/src/test/java/net/szum123321/test/textile_backup/TextileBackupTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.szum123321.test.textile_backup; - -import net.fabricmc.api.ModInitializer; - -public class TextileBackupTest implements ModInitializer { - @Override - public void onInitialize() { - - } -} diff --git a/src/test/resources/fabric.mod.json b/src/test/resources/fabric.mod.json deleted file mode 100644 index 809dace..0000000 --- a/src/test/resources/fabric.mod.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "schemaVersion": 1, - "id": "textile_backup", - "version": "${version}", - - "name": "Textile Backup Test", - "authors": [ - "Szum123321" - ], - "contact": { - "homepage": "https://www.curseforge.com/minecraft/mc-mods/textile-backup", - "issues": "https://github.com/Szum123321/textile_backup/issues", - "sources": "https://github.com/Szum123321/textile_backup" - }, - - "license": "GPLv3", - - "environment": "*", - "entrypoints": { - "main": [ - "net.szum123321.test.textile_backup.TextileBackupTest" - ] - }, - "mixins": [ - ], - - "depends": { - "fabricloader": ">=0.14.6", - "fabric": "*", - "minecraft": ">=1.19.1", - "cloth-config2": "*", - "java": ">=16", - "textile_backup": "*" - } -} \ No newline at end of file diff --git a/src/test/resources/textile_backup-test.mixins.json b/src/test/resources/textile_backup-test.mixins.json deleted file mode 100644 index f858f63..0000000 --- a/src/test/resources/textile_backup-test.mixins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": true, - "package": "net.szum123321.test.textile_backup.mixin", - "compatibilityLevel": "JAVA_16", - "mixins": [ - ], - "client": [ - ], - "injectors": { - "defaultRequire": 1 - } -}