Merge pull request #120 from Szum123321/validate_backups_research_1

Validate backups research 1
This commit is contained in:
Szum123321 2023-06-04 11:56:20 +02:00 committed by GitHub
commit 955fbd0f83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1482 additions and 872 deletions

View File

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

View File

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

View File

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

View File

@ -14,17 +14,20 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package net.szum123321.textile_backup;
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.ExecutorService;
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<Hash> CHECKSUM_SUPPLIER = BalticHash::new;/*() -> new Hash() {
private final CRC32 crc = new CRC32();
private ExecutorService executorService = null;// = Executors.newSingleThreadExecutor();
@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);
}
@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 class Globals {
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 class Globals {
public Optional<Path> 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 class Globals {
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;
}
}

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup;
@ -26,6 +26,7 @@ import net.fabricmc.api.ModInitializer;
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.commands.restore.RestoreBackupCommand;
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 class TextileBackup implements ModInitializer {
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
try {
ExecutableBackup.Builder
.newBackupContextBuilder()
.setServer(server)
.setInitiator(ActionInitiator.Shutdown)
.setComment("shutdown")
.announce()
.build()
).run();
.call();
} catch (Exception ignored) {}
}
});

View File

@ -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.Text;
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 @@ public class TextileLogger {
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 class TextileLogger {
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 class TextileLogger {
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 class TextileLogger {
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 class TextileLogger {
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);
}
}

View File

@ -14,7 +14,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package net.szum123321.textile_backup.client;

View File

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

View File

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

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup.commands.create;

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup.commands.create;
@ -25,8 +25,7 @@ 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.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 class StartBackupCommand {
}
private static int execute(ServerCommandSource source, @Nullable String comment) {
try {
Globals.INSTANCE.getQueueExecutor().submit(
MakeBackupRunnableFactory.create(
BackupContext.Builder
ExecutableBackup.Builder
.newBackupContextBuilder()
.setCommandSource(source)
.setComment(comment)
.guessInitiator()
.saveServer()
.build()
)
);
} catch (Exception e) {
log.error("Something went wrong while executing command!", e);
throw e;
}
return 1;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 class ConfigPOJO implements ConfigData {
}
}
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"),

View File

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

View File

@ -14,7 +14,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package net.szum123321.textile_backup.core;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Path, Exception> brokenFiles, LocalDateTime date, long startTimestamp, long finishTimestamp, String version) implements Serializable {
public static final String DATA_FILENAME = "textile_status.data";
public Optional<String> 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(); }
}

View File

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

View File

@ -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 @@ import java.io.IOException;
*/
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);
}
}

View File

@ -14,7 +14,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package net.szum123321.textile_backup.core;

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup.core;
@ -115,9 +115,9 @@ public class Utilities {
}
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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -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 class BackupScheduler {
if(nextBackup <= now) {
//It's time to run
Globals.INSTANCE.getQueueExecutor().submit(
MakeBackupRunnableFactory.create(
BackupContext.Builder
ExecutableBackup.Builder
.newBackupContextBuilder()
.setServer(server)
.setInitiator(ActionInitiator.Timer)
.saveServer()
.announce()
.build()
)
);
nextBackup = now + config.get().backupInterval;
@ -76,14 +75,13 @@ public class BackupScheduler {
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
ExecutableBackup.Builder
.newBackupContextBuilder()
.setServer(server)
.setInitiator(ActionInitiator.Timer)
.saveServer()
.announce()
.build()
)
);
scheduled = false;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Path, Exception> store = new HashMap<>();
public void handle(Path file, Exception e) { store.put(file, e); }
public boolean valid() { return store.isEmpty(); }
public Map<Path, Exception> get() {
return store;
}
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Path> 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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Path> getPath();
String getName();
long size() throws IOException;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
*/
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);
}
}

View File

@ -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<Path> 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);
.filter(Files::isRegularFile)
.toList();
if (ctx.initiator() == ActionInitiator.Player)
log.sendError(ctx, "Something went wrong while compressing files!");
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
);
}
});
}
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);
} 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");
}
} 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!");
} 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 @@ public abstract class AbstractCompressor {
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<Path> getPath() { return Optional.empty(); }
public String getName() { return CompressionStatus.DATA_FILENAME; }
public long size() { return data.length; }
public InputStream get() { return getInputStream(); }
}
}

View File

@ -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 @@ package net.szum123321.textile_backup.core.create.compressors;
import net.szum123321.textile_backup.TextileBackup;
import net.szum123321.textile_backup.TextileLogger;
import net.szum123321.textile_backup.core.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 class ParallelZipCompressor extends ZipCompressor {
}
@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(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 @@ public class ParallelZipCompressor extends ZipCompressor {
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 @@ public class ParallelZipCompressor extends ZipCompressor {
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());
}
}
}

View File

@ -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 @@ package net.szum123321.textile_backup.core.create.compressors;
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 class ZipCompressor extends AbstractCompressor {
}
@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 @@ public class ZipCompressor extends AbstractCompressor {
}
@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(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);

View File

@ -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 @@ public class AbstractTarArchiver extends AbstractCompressor {
}
@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);

View File

@ -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 class ParallelBZip2Compressor extends AbstractTarArchiver {
}
@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));
}
}

View File

@ -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 class ParallelGzipCompressor extends AbstractTarArchiver {
}
@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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 <a href="https://ticki.github.io/blog/seahash-explained/">SeaHash</a> 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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

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

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup.core.restore;
@ -24,18 +24,22 @@ import net.szum123321.textile_backup.TextileLogger;
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 class RestoreBackupRunnable implements Runnable {
log.info("Shutting down server...");
ctx.server().stop(false);
awaitServerShutdown();
Path worldFile = Utilities.getWorldFolder(ctx.server()),
tmp;
try {
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<Void> waitForShutdown = new FutureTask<>(() -> {
ctx.server().getThread().join(); //wait for server thread to die and save all its state
if(config.get().backupOldWorlds) {
MakeBackupRunnableFactory.create(
BackupContext.Builder
return ExecutableBackup.Builder
.newBackupContextBuilder()
.setServer(ctx.server())
.setInitiator(ActionInitiator.Restore)
.noCleanup()
.setComment("Old_World" + (ctx.comment() != null ? "_" + ctx.comment() : ""))
.build()
).run();
.announce()
.build().call();
}
return null;
});
Path worldFile = Utilities.getWorldFolder(ctx.server());
//run the thread.
new Thread(waitForShutdown, "Server shutdown wait thread").start();
try {
Path tmp = Files.createTempDirectory(
worldFile.getParent(),
ctx.restoreableFile().getFile().getFileName().toString());
log.info("Starting decompression...");
if (ctx.restoreableFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP)
ZipDecompressor.decompress(ctx.restoreableFile().getFile(), tmp);
else
GenericTarDecompressor.decompress(ctx.restoreableFile().getFile(), tmp);
long hash;
log.info("Deleting old world...");
if (ctx.restoreableFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP)
hash = ZipDecompressor.decompress(ctx.restoreableFile().getFile(), tmp);
else
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();
Optional<String> errorMsg;
if(Files.notExists(CompressionStatus.resolveStatusFilename(tmp))) {
errorMsg = Optional.of("Status file not found!");
} else {
CompressionStatus status = CompressionStatus.readFromFile(tmp);
log.info("Status: {}", status);
Files.delete(tmp.resolve(CompressionStatus.DATA_FILENAME));
errorMsg = status.validate(hash, ctx);
}
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 old backup");
log.info("Deleting restored backup file");
Files.delete(ctx.restoreableFile().getFile());
}
} catch (IOException e) {
} 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 class RestoreBackupRunnable implements Runnable {
log.info("Done!");
}
private void awaitServerShutdown() {
while(((LivingServer)ctx.server()).isAlive()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("Exception occurred!", e);
}
}
}
}

View File

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

View File

@ -1,19 +1,19 @@
/*
A simple backup mod for Fabric
Copyright (C) 2020 Szum123321
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
package net.szum123321.textile_backup.core.restore;

View File

@ -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 <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
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 @@ import java.time.Instant;
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 class GenericTarDecompressor {
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 class GenericTarDecompressor {
} 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 class GenericTarDecompressor {
}
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 {

View File

@ -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 <https://www.gnu.org/licenses/>.
* 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 <https://www.gnu.org/licenses/>.
*/
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 @@ import java.util.Iterator;
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<ZipArchiveEntry> it = zipFile.getEntries().asIterator(); it.hasNext(); ) {
ZipArchiveEntry entry = it.next();
@ -48,13 +52,21 @@ public class ZipDecompressor {
} 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;
}
}
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

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

View File

@ -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"
},

View File

@ -4,7 +4,6 @@
"compatibilityLevel": "JAVA_16",
"mixins": [
"DedicatedServerWatchdogMixin",
"MinecraftServerMixin",
"MinecraftServerSessionAccessor"
],
"client": [

View File

@ -1,10 +0,0 @@
package net.szum123321.test.textile_backup;
import net.fabricmc.api.ModInitializer;
public class TextileBackupTest implements ModInitializer {
@Override
public void onInitialize() {
}
}

View File

@ -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": "*"
}
}

View File

@ -1,12 +0,0 @@
{
"required": true,
"package": "net.szum123321.test.textile_backup.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}