Many, many changes:
- I migrated from cotton config to Cloth config + Auto config, that way it's easy to provide modmenu integration while keeping backward-compatibility. Because settings might change at runtime it's now necessary to keep the config reference up-to-date. I done this by using wrapper class around ConfigHolder<> and having a reference to main instance in each class accessing the config. - Switched form using one global logger to creating new instance in every class. - Removed BackupScheduler from Statics. Why did I put it there in the first place?
This commit is contained in:
parent
cc912d322e
commit
a8f98c460e
25
build.gradle
25
build.gradle
@ -16,6 +16,13 @@ minecraft {
|
||||
repositories{
|
||||
maven { url 'https://server.bbkr.space/artifactory/libs-release' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "https://maven.shedaniel.me/" }
|
||||
maven {
|
||||
url "https://maven.terraformersmc.com/releases/"
|
||||
content {
|
||||
includeGroup "com.terraformersmc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -27,20 +34,30 @@ dependencies {
|
||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modImplementation "io.github.cottonmc.cotton:cotton-config:1.0.0-rc.7"
|
||||
//General config library
|
||||
modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_version}") {
|
||||
exclude(group: "net.fabricmc.fabric-api")
|
||||
}
|
||||
|
||||
include "io.github.cottonmc:Jankson-Fabric:3.0.0+j1.2.0"
|
||||
include "io.github.cottonmc.cotton:cotton-logging:1.0.0-rc.4"
|
||||
include "io.github.cottonmc.cotton:cotton-config:1.0.0-rc.7"
|
||||
//Mod menu
|
||||
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
|
||||
|
||||
//General compression library
|
||||
modImplementation "org.apache.commons:commons-compress:1.19"
|
||||
include "org.apache.commons:commons-compress:1.19"
|
||||
|
||||
//LZMA support
|
||||
modImplementation "org.tukaani:xz:1.8"
|
||||
include "org.tukaani:xz:1.8"
|
||||
|
||||
//Gzip compression, parallel, GITHUB
|
||||
modImplementation 'com.github.shevek:parallelgzip:master-SNAPSHOT'
|
||||
include 'com.github.shevek:parallelgzip:master-SNAPSHOT'
|
||||
|
||||
// Lazy DFU makes the dev env start up much faster by loading DataFixerUpper lazily, which would otherwise take a long time. We rarely need it anyway.
|
||||
modRuntime("com.github.astei:lazydfu:${project.lazydfu_version}") {
|
||||
exclude(module: "fabric-loader")
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
@ -8,6 +8,14 @@ loader_version=0.11.5
|
||||
#Fabric api
|
||||
fabric_version=0.35.1+1.17
|
||||
|
||||
#Cloth Config
|
||||
cloth_version=5.0.34
|
||||
|
||||
#ModMenu
|
||||
modmenu_version=2.0.2
|
||||
|
||||
lazydfu_version=0.1.2
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 2.1.0
|
||||
maven_group = net.szum123321
|
||||
|
@ -1,154 +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;
|
||||
|
||||
import blue.endless.jankson.Comment;
|
||||
import io.github.cottonmc.cotton.config.annotations.ConfigFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@ConfigFile(name = Statics.MOD_ID)
|
||||
public class ConfigHandler {
|
||||
@Comment("\nTime between automatic backups in seconds\n" +
|
||||
"When set to 0 backups will not be performed automatically\n")
|
||||
public long backupInterval = 3600;
|
||||
|
||||
@Comment("\nDelay in seconds between typing-in /backup restore and it actually starting\n")
|
||||
public int restoreDelay = 30;
|
||||
|
||||
@Comment("\nShould backups be done even if there are no players?\n")
|
||||
public boolean doBackupsOnEmptyServer = false;
|
||||
|
||||
@Comment("\nShould backup be made on server shutdown?\n")
|
||||
public boolean shutdownBackup = true;
|
||||
|
||||
@Comment("\nShould world be backed up before restoring a backup?\n")
|
||||
public boolean backupOldWorlds = true;
|
||||
|
||||
@Comment("\nShould every world have its own backup folder?\n")
|
||||
public boolean perWorldBackup = true;
|
||||
|
||||
@Comment("\nA path to the backup folder\n")
|
||||
public String path = "backup/";
|
||||
|
||||
@Comment("\nThis setting allows you to exclude files form being backedup.\n"+
|
||||
"Be very careful when setting it, as it is easy corrupt your world!\n")
|
||||
public List<String> fileBlacklist = new ArrayList<>();
|
||||
|
||||
@Comment("\nShould backups be deleted after being restored?\n")
|
||||
public boolean deleteOldBackupAfterRestore = true;
|
||||
|
||||
@Comment("\nMaximum number of backups to keep. If set to 0 then no backup will be deleted based their amount\n")
|
||||
public int backupsToKeep = 10;
|
||||
|
||||
@Comment("\nMaximum age of backups to keep in seconds.\n If set to 0 then backups will not be deleted based their age \n")
|
||||
public long maxAge = 0;
|
||||
|
||||
@Comment("\nMaximum size of backup folder in kilo bytes (1024).\n" +
|
||||
"If set to 0 then backups will not be deleted\n")
|
||||
public int maxSize = 0;
|
||||
|
||||
@Comment("\nCompression level \n0 - 9\n Only affects zip compression.\n")
|
||||
public int compression = 7;
|
||||
|
||||
@Comment("\nLimit how many cores can be used for compression.\n" +
|
||||
"0 means that all available cores will be used\n")
|
||||
public int compressionCoreCountLimit = 0;
|
||||
|
||||
@Comment(value = "\nAvailable formats are:\n" +
|
||||
"ZIP - normal zip archive using standard deflate compression\n" +
|
||||
"GZIP - tar.gz using gzip compression\n" +
|
||||
"BZIP2 - tar.bz2 archive using bzip2 compression\n" +
|
||||
"LZMA - tar.xz using lzma compression\n" +
|
||||
"TAR - .tar with no compression\n")
|
||||
public ArchiveFormat format = ArchiveFormat.ZIP;
|
||||
|
||||
@Comment("\nMinimal permission level required to run commands\n")
|
||||
public int permissionLevel = 4;
|
||||
|
||||
@Comment("\nPlayer on singleplayer is always allowed to run command. Warning! On lan party everyone will be allowed to run it.\n")
|
||||
public boolean alwaysSingleplayerAllowed = true;
|
||||
|
||||
@Comment("\nPlayers allowed to run backup commands without sufficient permission level\n")
|
||||
public Set<String> playerWhitelist = new HashSet<>();
|
||||
|
||||
@Comment("\nPlayers banned from running backup commands besides their sufficient permission level\n")
|
||||
public Set<String> playerBlacklist = new HashSet<>();
|
||||
|
||||
@Comment("\nFormat of date&time used to name backup files.\n" +
|
||||
"Remember not to use '#' symbol or any other character that is not allowed by your operating system such as:\n" +
|
||||
"':', '\\', etc...\n" +
|
||||
"For more info: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html\n")
|
||||
public String dateTimeFormat = "yyyy.MM.dd_HH-mm-ss";
|
||||
|
||||
public Optional<String> sanitize() {
|
||||
if(compressionCoreCountLimit > Runtime.getRuntime().availableProcessors())
|
||||
return Optional.of("compressionCoreCountLimit is too big! Your system only has: " + Runtime.getRuntime().availableProcessors() + " cores!");
|
||||
|
||||
try {
|
||||
DateTimeFormatter.ofPattern(dateTimeFormat);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Optional.of("dateTimeFormat is wrong!\n" + e.getMessage() + "\n See: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html");
|
||||
}
|
||||
|
||||
File path = new File(Statics.CONFIG.path).getAbsoluteFile();
|
||||
|
||||
if (!path.exists()) {
|
||||
try {
|
||||
path.mkdirs();
|
||||
} catch (Exception e) {
|
||||
return Optional.of("Something went wrong while creating backup folder!\n" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public enum ArchiveFormat {
|
||||
ZIP("zip"),
|
||||
GZIP("tar", "gz"),
|
||||
BZIP2("tar", "bz2"),
|
||||
LZMA("tar", "xz"),
|
||||
TAR("tar");
|
||||
|
||||
private final List<String> extensionPieces;
|
||||
|
||||
ArchiveFormat(String... extensionParts) {
|
||||
extensionPieces = Arrays.asList(extensionParts);
|
||||
}
|
||||
|
||||
public String getCompleteString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
extensionPieces.forEach(s -> builder.append('.').append(s));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
boolean isMultipart() {
|
||||
return extensionPieces.size() > 1;
|
||||
}
|
||||
|
||||
public String getLastPiece() {
|
||||
return extensionPieces.get(extensionPieces.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,8 +18,6 @@
|
||||
|
||||
package net.szum123321.textile_backup;
|
||||
|
||||
import net.szum123321.textile_backup.core.CustomLogger;
|
||||
import net.szum123321.textile_backup.core.create.BackupScheduler;
|
||||
import net.szum123321.textile_backup.core.restore.AwaitThread;
|
||||
|
||||
import java.io.File;
|
||||
@ -30,16 +28,10 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class Statics {
|
||||
public static final String MOD_ID = "textile_backup";
|
||||
public static final String MOD_NAME = "Textile Backup";
|
||||
public static final CustomLogger LOGGER = new CustomLogger(MOD_ID, MOD_NAME);
|
||||
public static ConfigHandler CONFIG;
|
||||
|
||||
public static final BackupScheduler scheduler = new BackupScheduler();
|
||||
public static ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
public final static DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
|
||||
|
||||
public static ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true);
|
||||
public static boolean disableWatchdog = false;
|
||||
public static AwaitThread restoreAwaitThread = null;
|
||||
|
@ -19,8 +19,9 @@
|
||||
package net.szum123321.textile_backup;
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import io.github.cottonmc.cotton.config.ConfigManager;
|
||||
|
||||
import me.shedaniel.autoconfig.AutoConfig;
|
||||
import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
@ -34,40 +35,41 @@ import net.szum123321.textile_backup.commands.manage.WhitelistCommand;
|
||||
import net.szum123321.textile_backup.commands.restore.KillRestoreCommand;
|
||||
import net.szum123321.textile_backup.commands.manage.ListBackupsCommand;
|
||||
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.Utilities;
|
||||
import net.szum123321.textile_backup.core.create.BackupContext;
|
||||
import net.szum123321.textile_backup.core.create.BackupHelper;
|
||||
import net.szum123321.textile_backup.core.create.BackupScheduler;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class TextileBackup implements ModInitializer {
|
||||
public static final String MOD_NAME = "Textile Backup";
|
||||
public static final String MOD_ID = "textile_backup";
|
||||
|
||||
private final static TextileLogger log = new TextileLogger(MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Statics.LOGGER.info("Starting Textile Backup by Szum123321.");
|
||||
log.info("Starting Textile Backup by Szum123321");
|
||||
|
||||
Statics.CONFIG = ConfigManager.loadConfig(ConfigHandler.class);
|
||||
Optional<String> errorMessage = Statics.CONFIG.sanitize();
|
||||
|
||||
if(errorMessage.isPresent()) {
|
||||
Statics.LOGGER.fatal("TextileBackup config file has wrong settings!\n{}", errorMessage.get());
|
||||
System.exit(1);
|
||||
}
|
||||
ConfigHelper.updateInstance(AutoConfig.register(ConfigPOJO.class, JanksonConfigSerializer::new));
|
||||
|
||||
//TODO: finish writing wiki
|
||||
if(Statics.CONFIG.format == ConfigHandler.ArchiveFormat.ZIP) {
|
||||
if(config.get().format == ConfigPOJO.ArchiveFormat.ZIP) {
|
||||
Statics.tmpAvailable = Utilities.isTmpAvailable();
|
||||
if(!Statics.tmpAvailable) {
|
||||
Statics.LOGGER.warn("""
|
||||
log.warn("""
|
||||
WARNING! It seems like the temporary folder is not accessible on this system!
|
||||
This will cause problems with multithreaded zip compression, so a normal one will be used instead.
|
||||
For more info please read: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems""");
|
||||
}
|
||||
}
|
||||
|
||||
if(Statics.CONFIG.backupInterval > 0)
|
||||
ServerTickEvents.END_SERVER_TICK.register(Statics.scheduler::tick);
|
||||
ServerTickEvents.END_SERVER_TICK.register(new BackupScheduler()::tick);
|
||||
|
||||
//Restart Executor Service in singleplayer
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(ignored -> {
|
||||
@ -77,7 +79,7 @@ public class TextileBackup implements ModInitializer {
|
||||
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
|
||||
Statics.executorService.shutdown();
|
||||
|
||||
if (Statics.CONFIG.shutdownBackup && Statics.globalShutdownBackupFlag.get()) {
|
||||
if (config.get().shutdownBackup && Statics.globalShutdownBackupFlag.get()) {
|
||||
BackupHelper.create(
|
||||
BackupContext.Builder
|
||||
.newBackupContextBuilder()
|
||||
@ -93,11 +95,11 @@ public class TextileBackup implements ModInitializer {
|
||||
LiteralArgumentBuilder.<ServerCommandSource>literal("backup")
|
||||
.requires((ctx) -> {
|
||||
try {
|
||||
return ((Statics.CONFIG.playerWhitelist.contains(ctx.getEntityOrThrow().getEntityName()) ||
|
||||
ctx.hasPermissionLevel(Statics.CONFIG.permissionLevel)) &&
|
||||
!Statics.CONFIG.playerBlacklist.contains(ctx.getEntityOrThrow().getEntityName())) ||
|
||||
return ((config.get().playerWhitelist.contains(ctx.getEntityOrThrow().getEntityName()) ||
|
||||
ctx.hasPermissionLevel(config.get().permissionLevel)) &&
|
||||
!config.get().playerBlacklist.contains(ctx.getEntityOrThrow().getEntityName())) ||
|
||||
(ctx.getMinecraftServer().isSinglePlayer() &&
|
||||
Statics.CONFIG.alwaysSingleplayerAllowed);
|
||||
config.get().alwaysSingleplayerAllowed);
|
||||
} catch (Exception ignored) { //Command was called from server console.
|
||||
return true;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* A simple backup mod for Fabric
|
||||
* Copyright (C) 2020 Szum123321
|
||||
* Copyright (C) 2021 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
|
||||
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.szum123321.textile_backup.core;
|
||||
package net.szum123321.textile_backup;
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
@ -29,11 +29,12 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.MessageFactory;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessageFactory;
|
||||
import org.apache.logging.log4j.util.StackLocatorUtil;
|
||||
|
||||
/*
|
||||
This is practically just a copy-pate of Cotton's ModLogger with a few changes
|
||||
*/
|
||||
public class CustomLogger {
|
||||
public class TextileLogger {
|
||||
//private final boolean isDev = FabricLoader.getInstance().isDevelopmentEnvironment();
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
@ -42,12 +43,19 @@ public class CustomLogger {
|
||||
private final String prefix;
|
||||
private final MutableText prefixText;
|
||||
|
||||
public CustomLogger(String name, String prefix) {
|
||||
/* public TextileLogger(String name, String prefix) {
|
||||
this.messageFactory = ParameterizedMessageFactory.INSTANCE;
|
||||
this.logger = LogManager.getLogger(name, messageFactory);
|
||||
this.prefix = "[" + prefix + "]" + " ";
|
||||
this.prefixText = new LiteralText(this.prefix).styled(style -> style.withColor(0x5B23DA));
|
||||
}
|
||||
*/
|
||||
public TextileLogger(String prefix) {
|
||||
this.messageFactory = ParameterizedMessageFactory.INSTANCE;
|
||||
this.logger = LogManager.getLogger(StackLocatorUtil.getCallerClass(2), messageFactory);
|
||||
this.prefix = "[" + prefix + "]" + " ";
|
||||
this.prefixText = new LiteralText(this.prefix).styled(style -> style.withColor(0x5B23DA));
|
||||
}
|
||||
|
||||
public MutableText getPrefixText() {
|
||||
return prefixText.shallowCopy();
|
@ -0,0 +1,13 @@
|
||||
package net.szum123321.textile_backup.client;
|
||||
|
||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
import me.shedaniel.autoconfig.AutoConfig;
|
||||
import net.szum123321.textile_backup.config.ConfigPOJO;
|
||||
|
||||
public class ModMenuEntry implements ModMenuApi {
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return parent -> AutoConfig.getConfigScreen(ConfigPOJO.class, parent).get();
|
||||
}
|
||||
}
|
@ -21,19 +21,21 @@ package net.szum123321.textile_backup.commands.create;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.core.create.BackupHelper;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
|
||||
public class CleanupCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("cleanup")
|
||||
.executes(ctx -> execute(ctx.getSource()));
|
||||
}
|
||||
|
||||
private static int execute(ServerCommandSource source) {
|
||||
Statics.LOGGER.sendInfo(
|
||||
log.sendInfo(
|
||||
source,
|
||||
"Deleted: {} files.",
|
||||
BackupHelper.executeFileLimit(source, Utilities.getLevelName(source.getMinecraftServer()))
|
||||
|
@ -23,12 +23,16 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
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.BackupHelper;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class StartBackupCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("start")
|
||||
.then(CommandManager.argument("comment", StringArgumentType.string())
|
||||
@ -51,7 +55,7 @@ public class StartBackupCommand {
|
||||
)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Statics.LOGGER.error("Something went wrong while executing command!", e);
|
||||
log.error("Something went wrong while executing command!", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,18 @@ package net.szum123321.textile_backup.commands.manage;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import io.github.cottonmc.cotton.config.ConfigManager;
|
||||
import net.minecraft.command.argument.EntityArgumentType;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.config.ConfigHelper;
|
||||
|
||||
public class BlacklistCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("blacklist")
|
||||
.then(CommandManager.literal("add")
|
||||
@ -27,7 +31,7 @@ public class BlacklistCommand {
|
||||
}
|
||||
|
||||
private static int help(ServerCommandSource source) {
|
||||
Statics.LOGGER.sendInfo(source, "Available command are: add [player], remove [player], list.");
|
||||
log.sendInfo(source, "Available command are: add [player], remove [player], list.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -37,12 +41,12 @@ public class BlacklistCommand {
|
||||
|
||||
builder.append("Currently on the blacklist are: ");
|
||||
|
||||
for(String name : Statics.CONFIG.playerBlacklist){
|
||||
for(String name : config.get().playerBlacklist){
|
||||
builder.append(name);
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
Statics.LOGGER.sendInfo(source, builder.toString());
|
||||
log.sendInfo(source, builder.toString());
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -50,11 +54,11 @@ public class BlacklistCommand {
|
||||
private static int executeAdd(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(Statics.CONFIG.playerBlacklist.contains(player.getEntityName())) {
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} is already blacklisted.", player.getEntityName());
|
||||
if(config.get().playerBlacklist.contains(player.getEntityName())) {
|
||||
log.sendInfo(ctx.getSource(), "Player: {} is already blacklisted.", player.getEntityName());
|
||||
} else {
|
||||
Statics.CONFIG.playerBlacklist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(Statics.CONFIG);
|
||||
config.get().playerBlacklist.add(player.getEntityName());
|
||||
config.save();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@ -62,8 +66,8 @@ public class BlacklistCommand {
|
||||
builder.append(player.getEntityName());
|
||||
builder.append(" added to the blacklist");
|
||||
|
||||
if(Statics.CONFIG.playerWhitelist.contains(player.getEntityName())){
|
||||
Statics.CONFIG.playerWhitelist.remove(player.getEntityName());
|
||||
if(config.get().playerWhitelist.contains(player.getEntityName())){
|
||||
config.get().playerWhitelist.remove(player.getEntityName());
|
||||
builder.append(" and removed form the whitelist");
|
||||
}
|
||||
|
||||
@ -71,7 +75,7 @@ public class BlacklistCommand {
|
||||
|
||||
ctx.getSource().getMinecraftServer().getCommandManager().sendCommandTree(player);
|
||||
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), builder.toString());
|
||||
log.sendInfo(ctx.getSource(), builder.toString());
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -80,15 +84,15 @@ public class BlacklistCommand {
|
||||
private static int executeRemove(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(!Statics.CONFIG.playerBlacklist.contains(player.getEntityName())) {
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} newer was blacklisted.", player.getEntityName());
|
||||
if(!config.get().playerBlacklist.contains(player.getEntityName())) {
|
||||
log.sendInfo(ctx.getSource(), "Player: {} newer was blacklisted.", player.getEntityName());
|
||||
} else {
|
||||
Statics.CONFIG.playerBlacklist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(Statics.CONFIG);
|
||||
config.get().playerBlacklist.remove(player.getEntityName());
|
||||
config.save();
|
||||
|
||||
ctx.getSource().getMinecraftServer().getCommandManager().sendCommandTree(player);
|
||||
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} removed from the blacklist successfully.", player.getEntityName());
|
||||
log.sendInfo(ctx.getSource(), "Player: {} removed from the blacklist successfully.", player.getEntityName());
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -24,6 +24,8 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.commands.CommandExceptions;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.commands.FileSuggestionProvider;
|
||||
@ -36,6 +38,8 @@ import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DeleteCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("delete")
|
||||
.then(CommandManager.argument("file", StringArgumentType.word())
|
||||
@ -63,20 +67,20 @@ public class DeleteCommand {
|
||||
if(optionalFile.isPresent()) {
|
||||
if(Statics.untouchableFile.isEmpty() || !Statics.untouchableFile.get().equals(optionalFile.get())) {
|
||||
if(optionalFile.get().delete()) {
|
||||
Statics.LOGGER.sendInfo(source, "File {} successfully deleted!", optionalFile.get().getName());
|
||||
log.sendInfo(source, "File {} successfully deleted!", optionalFile.get().getName());
|
||||
|
||||
if(source.getEntity() instanceof PlayerEntity)
|
||||
Statics.LOGGER.info("Player {} deleted {}.", source.getPlayer().getName(), optionalFile.get().getName());
|
||||
log.info("Player {} deleted {}.", source.getPlayer().getName(), optionalFile.get().getName());
|
||||
} else {
|
||||
Statics.LOGGER.sendError(source, "Something went wrong while deleting file!");
|
||||
log.sendError(source, "Something went wrong while deleting file!");
|
||||
}
|
||||
} else {
|
||||
Statics.LOGGER.sendError(source, "Couldn't delete the file because it's being restored right now.");
|
||||
Statics.LOGGER.sendHint(source, "If you want to abort restoration then use: /backup killR");
|
||||
log.sendError(source, "Couldn't delete the file because it's being restored right now.");
|
||||
log.sendHint(source, "If you want to abort restoration then use: /backup killR");
|
||||
}
|
||||
} else {
|
||||
Statics.LOGGER.sendError(source, "Couldn't find file by this name.");
|
||||
Statics.LOGGER.sendHint(source, "Maybe try /backup list");
|
||||
log.sendError(source, "Couldn't find file by this name.");
|
||||
log.sendHint(source, "Maybe try /backup list");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -21,12 +21,15 @@ package net.szum123321.textile_backup.commands.manage;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.core.restore.RestoreHelper;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ListBackupsCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("list")
|
||||
.executes(ctx -> { StringBuilder builder = new StringBuilder();
|
||||
@ -50,7 +53,7 @@ public class ListBackupsCommand {
|
||||
}
|
||||
}
|
||||
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), builder.toString());
|
||||
log.sendInfo(ctx.getSource(), builder.toString());
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
@ -3,14 +3,18 @@ package net.szum123321.textile_backup.commands.manage;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import io.github.cottonmc.cotton.config.ConfigManager;
|
||||
import net.minecraft.command.argument.EntityArgumentType;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.config.ConfigHelper;
|
||||
|
||||
public class WhitelistCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register(){
|
||||
return CommandManager.literal("whitelist")
|
||||
.then(CommandManager.literal("add")
|
||||
@ -27,7 +31,7 @@ public class WhitelistCommand {
|
||||
}
|
||||
|
||||
private static int help(ServerCommandSource source){
|
||||
Statics.LOGGER.sendInfo(source, "Available command are: add [player], remove [player], list.");
|
||||
log.sendInfo(source, "Available command are: add [player], remove [player], list.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -37,12 +41,12 @@ public class WhitelistCommand {
|
||||
|
||||
builder.append("Currently on the whitelist are: ");
|
||||
|
||||
for(String name : Statics.CONFIG.playerWhitelist){
|
||||
for(String name : config.get().playerWhitelist){
|
||||
builder.append(name);
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
Statics.LOGGER.sendInfo(source, builder.toString());
|
||||
log.sendInfo(source, builder.toString());
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -50,11 +54,11 @@ public class WhitelistCommand {
|
||||
private static int executeAdd(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(Statics.CONFIG.playerWhitelist.contains(player.getEntityName())) {
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} is already whitelisted.", player.getEntityName());
|
||||
if(config.get().playerWhitelist.contains(player.getEntityName())) {
|
||||
log.sendInfo(ctx.getSource(), "Player: {} is already whitelisted.", player.getEntityName());
|
||||
} else {
|
||||
Statics.CONFIG.playerWhitelist.add(player.getEntityName());
|
||||
ConfigManager.saveConfig(Statics.CONFIG);
|
||||
config.get().playerWhitelist.add(player.getEntityName());
|
||||
config.save();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@ -62,8 +66,8 @@ public class WhitelistCommand {
|
||||
builder.append(player.getEntityName());
|
||||
builder.append(" added to the whitelist");
|
||||
|
||||
if(Statics.CONFIG.playerBlacklist.contains(player.getEntityName())){
|
||||
Statics.CONFIG.playerBlacklist.remove(player.getEntityName());
|
||||
if(config.get().playerBlacklist.contains(player.getEntityName())){
|
||||
config.get().playerBlacklist.remove(player.getEntityName());
|
||||
builder.append(" and removed form the blacklist");
|
||||
}
|
||||
|
||||
@ -71,7 +75,7 @@ public class WhitelistCommand {
|
||||
|
||||
ctx.getSource().getMinecraftServer().getCommandManager().sendCommandTree(player);
|
||||
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), builder.toString());
|
||||
log.sendInfo(ctx.getSource(), builder.toString());
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -80,15 +84,15 @@ public class WhitelistCommand {
|
||||
private static int executeRemove(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
|
||||
ServerPlayerEntity player = EntityArgumentType.getPlayer(ctx, "player");
|
||||
|
||||
if(!Statics.CONFIG.playerWhitelist.contains(player.getEntityName())) {
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} newer was whitelisted.", player.getEntityName());
|
||||
if(!config.get().playerWhitelist.contains(player.getEntityName())) {
|
||||
log.sendInfo(ctx.getSource(), "Player: {} newer was whitelisted.", player.getEntityName());
|
||||
} else {
|
||||
Statics.CONFIG.playerWhitelist.remove(player.getEntityName());
|
||||
ConfigManager.saveConfig(Statics.CONFIG);
|
||||
config.get().playerWhitelist.remove(player.getEntityName());
|
||||
config.save();
|
||||
|
||||
ctx.getSource().getMinecraftServer().getCommandManager().sendCommandTree(player);
|
||||
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Player: {} removed from the whitelist successfully.", player.getEntityName());
|
||||
log.sendInfo(ctx.getSource(), "Player: {} removed from the whitelist successfully.", player.getEntityName());
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -23,10 +23,13 @@ import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class KillRestoreCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("killR")
|
||||
.executes(ctx -> {
|
||||
@ -35,16 +38,16 @@ public class KillRestoreCommand {
|
||||
Statics.globalShutdownBackupFlag.set(true);
|
||||
Statics.untouchableFile = Optional.empty();
|
||||
|
||||
Statics.LOGGER.info("{} cancelled backup restoration.", ctx.getSource().getEntity() instanceof PlayerEntity ?
|
||||
log.info("{} cancelled backup restoration.", ctx.getSource().getEntity() instanceof PlayerEntity ?
|
||||
"Player: " + ctx.getSource().getName() :
|
||||
"SERVER"
|
||||
);
|
||||
|
||||
if(ctx.getSource().getEntity() instanceof PlayerEntity)
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Backup restoration successfully stopped.");
|
||||
log.sendInfo(ctx.getSource(), "Backup restoration successfully stopped.");
|
||||
|
||||
} else {
|
||||
Statics.LOGGER.sendInfo(ctx.getSource(), "Failed to stop backup restoration");
|
||||
log.sendInfo(ctx.getSource(), "Failed to stop backup restoration");
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
@ -24,6 +24,8 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.commands.CommandExceptions;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.commands.FileSuggestionProvider;
|
||||
@ -36,6 +38,8 @@ import java.time.format.DateTimeParseException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RestoreBackupCommand {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static LiteralArgumentBuilder<ServerCommandSource> register() {
|
||||
return CommandManager.literal("restore")
|
||||
.then(CommandManager.argument("file", StringArgumentType.word())
|
||||
@ -57,9 +61,9 @@ public class RestoreBackupCommand {
|
||||
).executes(context -> {
|
||||
ServerCommandSource source = context.getSource();
|
||||
|
||||
Statics.LOGGER.sendInfo(source, "To restore given backup you have to provide exact creation time in format:");
|
||||
Statics.LOGGER.sendInfo(source, "[YEAR]-[MONTH]-[DAY]_[HOUR].[MINUTE].[SECOND]");
|
||||
Statics.LOGGER.sendInfo(source, "Example: /backup restore 2020-08-05_10.58.33");
|
||||
log.sendInfo(source, "To restore given backup you have to provide exact creation time in format:");
|
||||
log.sendInfo(source, "[YEAR]-[MONTH]-[DAY]_[HOUR].[MINUTE].[SECOND]");
|
||||
log.sendInfo(source, "Example: /backup restore 2020-08-05_10.58.33");
|
||||
|
||||
return 1;
|
||||
});
|
||||
@ -78,9 +82,9 @@ public class RestoreBackupCommand {
|
||||
Optional<RestoreHelper.RestoreableFile> backupFile = RestoreHelper.findFileAndLockIfPresent(dateTime, source.getMinecraftServer());
|
||||
|
||||
if(backupFile.isPresent()) {
|
||||
Statics.LOGGER.info("Found file to restore {}", backupFile.get().getFile().getName());
|
||||
log.info("Found file to restore {}", backupFile.get().getFile().getName());
|
||||
} else {
|
||||
Statics.LOGGER.sendInfo(source, "No file created on {} was found!", dateTime.format(Statics.defaultDateTimeFormatter));
|
||||
log.sendInfo(source, "No file created on {} was found!", dateTime.format(Statics.defaultDateTimeFormatter));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -97,7 +101,7 @@ public class RestoreBackupCommand {
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
Statics.LOGGER.sendInfo(source, "Someone has already started another restoration.");
|
||||
log.sendInfo(source, "Someone has already started another restoration.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* A simple backup mod for Fabric
|
||||
* Copyright (C) 2021 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.config;
|
||||
|
||||
import me.shedaniel.autoconfig.ConfigHolder;
|
||||
|
||||
public class ConfigHelper {
|
||||
public static final ConfigHelper INSTANCE = new ConfigHelper();
|
||||
private ConfigHolder<ConfigPOJO> configHolder;
|
||||
|
||||
public static void updateInstance(ConfigHolder<ConfigPOJO> ch) { INSTANCE.configHolder = ch; }
|
||||
|
||||
public ConfigPOJO get() { return configHolder.get(); }
|
||||
|
||||
public void save() { configHolder.save(); }
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* A simple backup mod for Fabric
|
||||
* Copyright (C) 2021 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.config;
|
||||
|
||||
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.*;
|
||||
|
||||
@Config(name = TextileBackup.MOD_ID)
|
||||
public class ConfigPOJO implements ConfigData {
|
||||
@Comment("""
|
||||
Format of date&time used to name backup files.
|
||||
Remember not to use '#' symbol or any other character that is not allowed by your operating system such as:
|
||||
':', '\\', etc...
|
||||
For more info: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html""")
|
||||
public String dateTimeFormat = "yyyy.MM.dd_HH-mm-ss";
|
||||
|
||||
@Comment("Should every world have its own backup folder?")
|
||||
@ConfigEntry.Gui.Excluded
|
||||
public boolean perWorldBackup = true;
|
||||
|
||||
@Comment("A path to the backup folder")
|
||||
public String path = "backup/";
|
||||
|
||||
@Comment("""
|
||||
This setting allows you to exclude files form being backed-up.
|
||||
Be very careful when setting it, as it is easy corrupt your world!""")
|
||||
public List<String> fileBlacklist = new ArrayList<>();
|
||||
|
||||
@Comment("Should backups be deleted after being restored?")
|
||||
public boolean deleteOldBackupAfterRestore = true;
|
||||
|
||||
@Comment("Maximum number of backups to keep.\nIf set to 0 then no backup will be deleted based their amount")
|
||||
public int backupsToKeep = 10;
|
||||
|
||||
@Comment("""
|
||||
Maximum age of backups to keep in seconds.
|
||||
If set to 0 then backups will not be deleted based their age""")
|
||||
public long maxAge = 0;
|
||||
|
||||
@Comment("""
|
||||
Maximum size of backup folder in kilo bytes (1024).
|
||||
If set to 0 then backups will not be deleted""")
|
||||
public int maxSize = 0;
|
||||
|
||||
@Comment("""
|
||||
Time between automatic backups in seconds
|
||||
When set to 0 backups will not be performed automatically""")
|
||||
@ConfigEntry.Gui.Tooltip()
|
||||
@ConfigEntry.Category("Create")
|
||||
public long backupInterval = 3600;
|
||||
|
||||
@Comment("Should backups be done even if there are no players?")
|
||||
@ConfigEntry.Category("Create")
|
||||
public boolean doBackupsOnEmptyServer = false;
|
||||
|
||||
@Comment("Should backup be made on server shutdown?")
|
||||
@ConfigEntry.Category("Create")
|
||||
public boolean shutdownBackup = true;
|
||||
|
||||
@Comment("Should world be backed up before restoring a backup?")
|
||||
@ConfigEntry.Category("Create")
|
||||
public boolean backupOldWorlds = true;
|
||||
|
||||
@Comment("Compression level 0 - 9 Only affects zip compression.")
|
||||
@ConfigEntry.BoundedDiscrete(max = 9)
|
||||
@ConfigEntry.Category("Create")
|
||||
public int compression = 7;
|
||||
|
||||
@Comment("""
|
||||
Limit how many cores can be used for compression.
|
||||
0 means that all available cores will be used""")
|
||||
@ConfigEntry.Category("Create")
|
||||
public int compressionCoreCountLimit = 0;
|
||||
|
||||
@Comment(value = """
|
||||
Available 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.Category("Create")
|
||||
@ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON)
|
||||
public ArchiveFormat format = ArchiveFormat.ZIP;
|
||||
|
||||
@Comment("Minimal permission level required to run commands")
|
||||
@ConfigEntry.Category("Manage")
|
||||
public int permissionLevel = 4;
|
||||
|
||||
@Comment("""
|
||||
Player on singleplayer is always allowed to run command.
|
||||
Warning! On lan party everyone will be allowed to run it.""")
|
||||
@ConfigEntry.Category("Manage")
|
||||
public boolean alwaysSingleplayerAllowed = true;
|
||||
|
||||
@Comment("Players allowed to run backup commands without sufficient permission level")
|
||||
@ConfigEntry.Category("Manage")
|
||||
public Set<String> playerWhitelist = new HashSet<>();
|
||||
|
||||
@Comment("Players banned from running backup commands besides their sufficient permission level")
|
||||
@ConfigEntry.Category("Manage")
|
||||
public Set<String> playerBlacklist = new HashSet<>();
|
||||
|
||||
@Comment("Delay in seconds between typing-in /backup restore and it actually starting")
|
||||
@ConfigEntry.Category("Restore")
|
||||
public int restoreDelay = 30;
|
||||
|
||||
@Override
|
||||
public void validatePostLoad() throws ValidationException {
|
||||
if(compressionCoreCountLimit > Runtime.getRuntime().availableProcessors())
|
||||
throw new ValidationException("compressionCoreCountLimit is too high! Your system only has: " + Runtime.getRuntime().availableProcessors() + " cores!");
|
||||
|
||||
try {
|
||||
DateTimeFormatter.ofPattern(dateTimeFormat);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ValidationException(
|
||||
"dateTimeFormat is wrong! See: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ArchiveFormat {
|
||||
ZIP("zip"),
|
||||
GZIP("tar", "gz"),
|
||||
BZIP2("tar", "bz2"),
|
||||
LZMA("tar", "xz"),
|
||||
TAR("tar");
|
||||
|
||||
private final List<String> extensionPieces;
|
||||
|
||||
ArchiveFormat(String... extensionParts) {
|
||||
extensionPieces = Arrays.asList(extensionParts);
|
||||
}
|
||||
|
||||
public String getCompleteString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
extensionPieces.forEach(s -> builder.append('.').append(s));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
boolean isMultipart() {
|
||||
return extensionPieces.size() > 1;
|
||||
}
|
||||
|
||||
public String getLastPiece() {
|
||||
return extensionPieces.get(extensionPieces.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@ package net.szum123321.textile_backup.core;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.szum123321.textile_backup.ConfigHandler;
|
||||
import net.szum123321.textile_backup.config.ConfigHelper;
|
||||
import net.szum123321.textile_backup.config.ConfigPOJO;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.mixin.MinecraftServerSessionAccessor;
|
||||
|
||||
@ -36,6 +37,8 @@ import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Utilities {
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static String getLevelName(MinecraftServer server) {
|
||||
return ((MinecraftServerSessionAccessor)server).getSession().getDirectoryName();
|
||||
}
|
||||
@ -47,18 +50,16 @@ public class Utilities {
|
||||
}
|
||||
|
||||
public static File getBackupRootPath(String worldName) {
|
||||
File path = new File(Statics.CONFIG.path).getAbsoluteFile();
|
||||
File path = new File(config.get().path).getAbsoluteFile();
|
||||
|
||||
if (Statics.CONFIG.perWorldBackup)
|
||||
path = path.toPath().resolve(worldName).toFile();
|
||||
if (config.get().perWorldBackup) path = path.toPath().resolve(worldName).toFile();
|
||||
|
||||
if (!path.exists()) {
|
||||
path.mkdirs();
|
||||
}
|
||||
if (!path.exists()) path.mkdirs();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
//This is quite pointless
|
||||
public static boolean isTmpAvailable() {
|
||||
try {
|
||||
File tmp = File.createTempFile("textile_backup_tmp_test", String.valueOf(Instant.now().getEpochSecond()));
|
||||
@ -88,26 +89,23 @@ public class Utilities {
|
||||
|
||||
public static boolean isBlacklisted(Path path) {
|
||||
if(isWindows()) { //hotfix!
|
||||
if (path.getFileName().toString().equals("session.lock")) {
|
||||
Statics.LOGGER.trace("Skipping session.lock");
|
||||
return true;
|
||||
}
|
||||
if (path.getFileName().toString().equals("session.lock")) return true;
|
||||
}
|
||||
|
||||
for(String i : Statics.CONFIG.fileBlacklist) if(path.startsWith(i)) return true;
|
||||
for(String i : config.get().fileBlacklist) if(path.startsWith(i)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Optional<ConfigHandler.ArchiveFormat> getArchiveExtension(String fileName) {
|
||||
public static Optional<ConfigPOJO.ArchiveFormat> getArchiveExtension(String fileName) {
|
||||
String[] parts = fileName.split("\\.");
|
||||
|
||||
return Arrays.stream(ConfigHandler.ArchiveFormat.values())
|
||||
return Arrays.stream(ConfigPOJO.ArchiveFormat.values())
|
||||
.filter(format -> format.getLastPiece().equals(parts[parts.length - 1]))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public static Optional<ConfigHandler.ArchiveFormat> getArchiveExtension(File f) {
|
||||
public static Optional<ConfigPOJO.ArchiveFormat> getArchiveExtension(File f) {
|
||||
return getArchiveExtension(f.getName());
|
||||
}
|
||||
|
||||
@ -155,7 +153,7 @@ public class Utilities {
|
||||
}
|
||||
|
||||
public static DateTimeFormatter getDateTimeFormatter() {
|
||||
return DateTimeFormatter.ofPattern(Statics.CONFIG.dateTimeFormat);
|
||||
return DateTimeFormatter.ofPattern(config.get().dateTimeFormat);
|
||||
}
|
||||
|
||||
public static DateTimeFormatter getBackupDateTimeFormatter() {
|
||||
|
@ -25,6 +25,9 @@ import net.minecraft.text.MutableText;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.Util;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
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.Utilities;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -37,6 +40,9 @@ import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BackupHelper {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static Runnable create(BackupContext ctx) {
|
||||
notifyPlayers(ctx);
|
||||
|
||||
@ -55,17 +61,17 @@ public class BackupHelper {
|
||||
builder.append(" on: ");
|
||||
builder.append(Utilities.getDateTimeFormatter().format(LocalDateTime.now()));
|
||||
|
||||
Statics.LOGGER.info(builder.toString());
|
||||
log.info(builder.toString());
|
||||
|
||||
if (ctx.shouldSave()) {
|
||||
Statics.LOGGER.sendInfoAL(ctx, "Saving server...");
|
||||
log.sendInfoAL(ctx, "Saving server...");
|
||||
|
||||
ctx.getServer().getPlayerManager().saveAllPlayerData();
|
||||
|
||||
try {
|
||||
ctx.getServer().save(false, true, true);
|
||||
} catch (Exception e) {
|
||||
Statics.LOGGER.sendErrorAL(ctx,"An exception occurred when trying to save the world!");
|
||||
log.sendErrorAL(ctx,"An exception occurred when trying to save the world!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +79,7 @@ public class BackupHelper {
|
||||
}
|
||||
|
||||
private static void notifyPlayers(BackupContext ctx) {
|
||||
MutableText message = Statics.LOGGER.getPrefixText();
|
||||
MutableText message = log.getPrefixText();
|
||||
message.append(new LiteralText("Warning! Server backup will begin shortly. You may experience some lag.").formatted(Formatting.WHITE));
|
||||
|
||||
UUID uuid;
|
||||
@ -94,30 +100,30 @@ public class BackupHelper {
|
||||
int deletedFiles = 0;
|
||||
|
||||
if (root.isDirectory() && root.exists() && root.listFiles() != null) {
|
||||
if (Statics.CONFIG.maxAge > 0) { // delete files older that configured
|
||||
if (config.get().maxAge > 0) { // delete files older that configured
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
deletedFiles += Arrays.stream(root.listFiles())
|
||||
.filter(Utilities::isValidBackup)// We check if we can get file's creation date so that the next line won't throw an exception
|
||||
.filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > Statics.CONFIG.maxAge)
|
||||
.filter(f -> now.toEpochSecond(ZoneOffset.UTC) - Utilities.getFileCreationTime(f).get().toEpochSecond(ZoneOffset.UTC) > config.get().maxAge)
|
||||
.map(f -> deleteFile(f, ctx))
|
||||
.filter(b -> b).count(); //a bit awkward
|
||||
}
|
||||
|
||||
if (Statics.CONFIG.backupsToKeep > 0 && root.listFiles().length > Statics.CONFIG.backupsToKeep) {
|
||||
if (config.get().backupsToKeep > 0 && root.listFiles().length > config.get().backupsToKeep) {
|
||||
deletedFiles += Arrays.stream(root.listFiles())
|
||||
.filter(Utilities::isValidBackup)
|
||||
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime((File) f).get()).reversed())
|
||||
.skip(Statics.CONFIG.backupsToKeep)
|
||||
.skip(config.get().backupsToKeep)
|
||||
.map(f -> deleteFile(f, ctx))
|
||||
.filter(b -> b).count();
|
||||
}
|
||||
|
||||
if (Statics.CONFIG.maxSize > 0 && FileUtils.sizeOfDirectory(root) / 1024 > Statics.CONFIG.maxSize) {
|
||||
if (config.get().maxSize > 0 && FileUtils.sizeOfDirectory(root) / 1024 > config.get().maxSize) {
|
||||
deletedFiles += Arrays.stream(root.listFiles())
|
||||
.filter(Utilities::isValidBackup)
|
||||
.sorted(Comparator.comparing(f -> Utilities.getFileCreationTime(f).get()))
|
||||
.takeWhile(f -> FileUtils.sizeOfDirectory(root) / 1024 > Statics.CONFIG.maxSize)
|
||||
.takeWhile(f -> FileUtils.sizeOfDirectory(root) / 1024 > config.get().maxSize)
|
||||
.map(f -> deleteFile(f, ctx))
|
||||
.filter(b -> b).count();
|
||||
}
|
||||
@ -129,10 +135,10 @@ public class BackupHelper {
|
||||
private static boolean deleteFile(File f, ServerCommandSource ctx) {
|
||||
if(Statics.untouchableFile.isEmpty()|| !Statics.untouchableFile.get().equals(f)) {
|
||||
if(f.delete()) {
|
||||
Statics.LOGGER.sendInfoAL(ctx, "Deleting: {}", f.getName());
|
||||
log.sendInfoAL(ctx, "Deleting: {}", f.getName());
|
||||
return true;
|
||||
} else {
|
||||
Statics.LOGGER.sendErrorAL(ctx, "Something went wrong while deleting: {}.", f.getName());
|
||||
log.sendErrorAL(ctx, "Something went wrong while deleting: {}.", f.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,14 @@ package net.szum123321.textile_backup.core.create;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.config.ConfigHelper;
|
||||
import net.szum123321.textile_backup.core.ActionInitiator;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class BackupScheduler {
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
private boolean scheduled;
|
||||
private long nextBackup;
|
||||
|
||||
@ -34,9 +37,10 @@ public class BackupScheduler {
|
||||
}
|
||||
|
||||
public void tick(MinecraftServer server) {
|
||||
if(config.get().backupInterval < 1) return;
|
||||
long now = Instant.now().getEpochSecond();
|
||||
|
||||
if(Statics.CONFIG.doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
||||
if(config.get().doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
||||
if(scheduled) {
|
||||
if(nextBackup <= now) {
|
||||
Statics.executorService.submit(
|
||||
@ -50,13 +54,13 @@ public class BackupScheduler {
|
||||
)
|
||||
);
|
||||
|
||||
nextBackup = now + Statics.CONFIG.backupInterval;
|
||||
nextBackup = now + config.get().backupInterval;
|
||||
}
|
||||
} else {
|
||||
nextBackup = now + Statics.CONFIG.backupInterval;
|
||||
nextBackup = now + config.get().backupInterval;
|
||||
scheduled = true;
|
||||
}
|
||||
} else if(!Statics.CONFIG.doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
||||
} else if(!config.get().doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
||||
if(scheduled && nextBackup <= now) {
|
||||
Statics.executorService.submit(
|
||||
BackupHelper.create(
|
||||
|
@ -19,6 +19,9 @@
|
||||
package net.szum123321.textile_backup.core.create;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
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.create.compressors.*;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
@ -33,6 +36,9 @@ import java.io.OutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
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){
|
||||
@ -45,11 +51,11 @@ public class MakeBackupRunnable implements Runnable {
|
||||
Utilities.disableWorldSaving(context.getServer());
|
||||
Statics.disableWatchdog = true;
|
||||
|
||||
Statics.LOGGER.sendInfoAL(context, "Starting backup");
|
||||
log.sendInfoAL(context, "Starting backup");
|
||||
|
||||
File world = Utilities.getWorldFolder(context.getServer());
|
||||
|
||||
Statics.LOGGER.trace("Minecraft world is: {}", world);
|
||||
log.trace("Minecraft world is: {}", world);
|
||||
|
||||
File outFile = Utilities
|
||||
.getBackupRootPath(Utilities.getLevelName(context.getServer()))
|
||||
@ -57,32 +63,32 @@ public class MakeBackupRunnable implements Runnable {
|
||||
.resolve(getFileName())
|
||||
.toFile();
|
||||
|
||||
Statics.LOGGER.trace("Outfile is: {}", outFile);
|
||||
log.trace("Outfile is: {}", outFile);
|
||||
|
||||
outFile.getParentFile().mkdirs();
|
||||
|
||||
try {
|
||||
outFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred when trying to create new backup file!", e);
|
||||
log.error("An exception occurred when trying to create new backup file!", e);
|
||||
|
||||
if(context.getInitiator() == ActionInitiator.Player)
|
||||
Statics.LOGGER.sendError(context, "An exception occurred when trying to create new backup file!");
|
||||
log.sendError(context, "An exception occurred when trying to create new backup file!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int coreCount;
|
||||
|
||||
if(Statics.CONFIG.compressionCoreCountLimit <= 0) {
|
||||
if(config.get().compressionCoreCountLimit <= 0) {
|
||||
coreCount = Runtime.getRuntime().availableProcessors();
|
||||
} else {
|
||||
coreCount = Math.min(Statics.CONFIG.compressionCoreCountLimit, Runtime.getRuntime().availableProcessors());
|
||||
coreCount = Math.min(config.get().compressionCoreCountLimit, Runtime.getRuntime().availableProcessors());
|
||||
}
|
||||
|
||||
Statics.LOGGER.trace("Running compression on {} threads. Available cores: {}", coreCount, Runtime.getRuntime().availableProcessors());
|
||||
log.trace("Running compression on {} threads. Available cores: {}", coreCount, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
switch (Statics.CONFIG.format) {
|
||||
switch (config.get().format) {
|
||||
case ZIP -> {
|
||||
if (Statics.tmpAvailable && coreCount > 1)
|
||||
ParallelZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
|
||||
@ -98,16 +104,16 @@ public class MakeBackupRunnable implements Runnable {
|
||||
}.createArchive(world, outFile, context, coreCount);
|
||||
case TAR -> new AbstractTarArchiver().createArchive(world, outFile, context, coreCount);
|
||||
default -> {
|
||||
Statics.LOGGER.warn("Specified compressor ({}) is not supported! Zip will be used instead!", Statics.CONFIG.format);
|
||||
log.warn("Specified compressor ({}) is not supported! Zip will be used instead!", config.get().format);
|
||||
if (context.getInitiator() == ActionInitiator.Player)
|
||||
Statics.LOGGER.sendError(context.getCommandSource(), "Error! No correct compression format specified! Using default compressor!");
|
||||
log.sendError(context.getCommandSource(), "Error! No correct compression format specified! Using default compressor!");
|
||||
ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
|
||||
}
|
||||
}
|
||||
|
||||
BackupHelper.executeFileLimit(context.getCommandSource(), Utilities.getLevelName(context.getServer()));
|
||||
|
||||
Statics.LOGGER.sendInfoAL(context, "Done!");
|
||||
log.sendInfoAL(context, "Done!");
|
||||
} finally {
|
||||
Utilities.enableWorldSaving(context.getServer());
|
||||
Statics.disableWatchdog = false;
|
||||
@ -119,6 +125,6 @@ public class MakeBackupRunnable implements Runnable {
|
||||
|
||||
return Utilities.getDateTimeFormatter().format(now) +
|
||||
(context.getComment() != null ? "#" + context.getComment().replace("#", "") : "") +
|
||||
Statics.CONFIG.format.getCompleteString();
|
||||
config.get().format.getCompleteString();
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.create.compressors;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
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;
|
||||
@ -32,6 +33,8 @@ import java.time.Instant;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class AbstractCompressor {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public void createArchive(File inputFile, File outputFile, BackupContext ctx, int coreLimit) {
|
||||
Instant start = Instant.now();
|
||||
|
||||
@ -48,35 +51,35 @@ public abstract class AbstractCompressor {
|
||||
//hopefully one broken file won't spoil the whole archive
|
||||
addEntry(file, inputFile.toPath().relativize(file.toPath()).toString(), arc);
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred while trying to compress: {}", inputFile.toPath().relativize(file.toPath()).toString(), e);
|
||||
log.error("An exception occurred while trying to compress: {}", inputFile.toPath().relativize(file.toPath()).toString(), e);
|
||||
|
||||
if (ctx.getInitiator() == ActionInitiator.Player)
|
||||
Statics.LOGGER.sendError(ctx, "Something went wrong while compressing files!");
|
||||
log.sendError(ctx, "Something went wrong while compressing files!");
|
||||
}
|
||||
});
|
||||
|
||||
finish(arc);
|
||||
} catch(NoSpaceLeftOnDeviceException e) {
|
||||
Statics.LOGGER.error("CRITICAL ERROR OCCURRED!");
|
||||
Statics.LOGGER.error("The backup is corrupted.");
|
||||
Statics.LOGGER.error("Don't panic! This is a known issue!");
|
||||
Statics.LOGGER.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems");
|
||||
Statics.LOGGER.error("In case this isn't it here's also the exception itself!", e);
|
||||
log.error("CRITICAL ERROR OCCURRED!");
|
||||
log.error("The backup is corrupted.");
|
||||
log.error("Don't panic! This is a known issue!");
|
||||
log.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems");
|
||||
log.error("In case this isn't it here's also the exception itself!", e);
|
||||
|
||||
if(ctx.getInitiator() == ActionInitiator.Player) {
|
||||
Statics.LOGGER.sendError(ctx, "Backup failed. The file is corrupt.");
|
||||
Statics.LOGGER.error("For help see: https://github.com/Szum123321/textile_backup/wiki/ZIP-Problems");
|
||||
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) {
|
||||
Statics.LOGGER.error("An exception occurred!", e);
|
||||
log.error("An exception occurred!", e);
|
||||
} catch (Exception e) {
|
||||
if(ctx.getInitiator() == ActionInitiator.Player)
|
||||
Statics.LOGGER.sendError(ctx, "Something went wrong while compressing files!");
|
||||
log.sendError(ctx, "Something went wrong while compressing files!");
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
Statics.LOGGER.sendInfoAL(ctx, "Compression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));
|
||||
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;
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.create.compressors;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
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 org.apache.commons.compress.archivers.zip.*;
|
||||
@ -36,6 +37,8 @@ import java.util.zip.ZipEntry;
|
||||
https://stackoverflow.com/users/2987755/dkb
|
||||
*/
|
||||
public class ParallelZipCompressor extends ZipCompressor {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
//These fields are used to discriminate against the issue #51
|
||||
private final static SimpleStackTraceElement[] STACKTRACE = {
|
||||
new SimpleStackTraceElement("sun.nio.ch.FileDispatcherImpl", "write0", true),
|
||||
@ -130,7 +133,7 @@ public class ParallelZipCompressor extends ZipCompressor {
|
||||
try {
|
||||
return new FileInputStream(sourceFile);
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred while trying to create an input stream from file: {}!", sourceFile.getName(), e);
|
||||
log.error("An exception occurred while trying to create an input stream from file: {}!", sourceFile.getName(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.create.compressors;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.config.ConfigHelper;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
import net.szum123321.textile_backup.core.create.BackupContext;
|
||||
import org.apache.commons.compress.archivers.zip.Zip64Mode;
|
||||
@ -27,12 +27,13 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.utils.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
public class ZipCompressor extends AbstractCompressor {
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static ZipCompressor getInstance() {
|
||||
return new ZipCompressor();
|
||||
}
|
||||
@ -43,7 +44,7 @@ public class ZipCompressor extends AbstractCompressor {
|
||||
|
||||
arc.setMethod(ZipArchiveOutputStream.DEFLATED);
|
||||
arc.setUseZip64(Zip64Mode.AsNeeded);
|
||||
arc.setLevel(Statics.CONFIG.compression);
|
||||
arc.setLevel(config.get().compression);
|
||||
arc.setComment("Created on: " + Utilities.getDateTimeFormatter().format(LocalDateTime.now()));
|
||||
|
||||
return arc;
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.restore;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@ -26,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
This thread waits some amount of time and then starts a new, independent thread
|
||||
*/
|
||||
public class AwaitThread extends Thread {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static AtomicInteger threadCounter = new AtomicInteger(0);
|
||||
|
||||
private final int delay;
|
||||
@ -40,13 +42,13 @@ public class AwaitThread extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Statics.LOGGER.info("Countdown begins... Waiting {} second.", delay);
|
||||
log.info("Countdown begins... Waiting {} second.", delay);
|
||||
|
||||
// 𝄞 This is final count down! Tu ruru Tu, Tu Ru Tu Tu ♪
|
||||
try {
|
||||
Thread.sleep(delay * 1000L);
|
||||
} catch (InterruptedException e) {
|
||||
Statics.LOGGER.info("Backup restoration cancelled.");
|
||||
log.info("Backup restoration cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,10 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.restore;
|
||||
|
||||
import net.szum123321.textile_backup.ConfigHandler;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
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.Statics;
|
||||
@ -31,6 +34,9 @@ import net.szum123321.textile_backup.core.restore.decompressors.ZipDecompressor;
|
||||
import java.io.File;
|
||||
|
||||
public class RestoreBackupRunnable implements Runnable {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
private final RestoreContext ctx;
|
||||
|
||||
public RestoreBackupRunnable(RestoreContext ctx) {
|
||||
@ -41,12 +47,12 @@ public class RestoreBackupRunnable implements Runnable {
|
||||
public void run() {
|
||||
Statics.globalShutdownBackupFlag.set(false);
|
||||
|
||||
Statics.LOGGER.info("Shutting down server...");
|
||||
log.info("Shutting down server...");
|
||||
|
||||
ctx.getServer().stop(false);
|
||||
awaitServerShutdown();
|
||||
|
||||
if(Statics.CONFIG.backupOldWorlds) {
|
||||
if(config.get().backupOldWorlds) {
|
||||
BackupHelper.create(
|
||||
BackupContext.Builder
|
||||
.newBackupContextBuilder()
|
||||
@ -59,31 +65,35 @@ public class RestoreBackupRunnable implements Runnable {
|
||||
|
||||
File worldFile = Utilities.getWorldFolder(ctx.getServer());
|
||||
|
||||
Statics.LOGGER.info("Deleting old world...");
|
||||
log.info("Deleting old world...");
|
||||
|
||||
if(!deleteDirectory(worldFile))
|
||||
Statics.LOGGER.error("Something went wrong while deleting old world!");
|
||||
log.error("Something went wrong while deleting old world!");
|
||||
|
||||
worldFile.mkdirs();
|
||||
|
||||
Statics.LOGGER.info("Starting decompression...");
|
||||
log.info("Starting decompression...");
|
||||
|
||||
if(ctx.getFile().getArchiveFormat() == ConfigHandler.ArchiveFormat.ZIP)
|
||||
if(ctx.getFile().getArchiveFormat() == ConfigPOJO.ArchiveFormat.ZIP)
|
||||
ZipDecompressor.decompress(ctx.getFile().getFile(), worldFile);
|
||||
else
|
||||
GenericTarDecompressor.decompress(ctx.getFile().getFile(), worldFile);
|
||||
|
||||
if(Statics.CONFIG.deleteOldBackupAfterRestore) {
|
||||
Statics.LOGGER.info("Deleting old backup");
|
||||
if(config.get().deleteOldBackupAfterRestore) {
|
||||
log.info("Deleting old backup");
|
||||
|
||||
if(!ctx.getFile().getFile().delete())
|
||||
Statics.LOGGER.info("Something went wrong while deleting old backup");
|
||||
log.info("Something went wrong while deleting old backup");
|
||||
}
|
||||
|
||||
//in case we're playing on client
|
||||
Statics.globalShutdownBackupFlag.set(true);
|
||||
|
||||
Statics.LOGGER.info("Done!");
|
||||
log.info("Done!");
|
||||
|
||||
//Might solve #37
|
||||
//Idk if it's a good idea...
|
||||
//Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
||||
private void awaitServerShutdown() {
|
||||
@ -91,7 +101,7 @@ public class RestoreBackupRunnable implements Runnable {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Statics.LOGGER.error("Exception occurred!", e);
|
||||
log.error("Exception occurred!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,10 @@ import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.Util;
|
||||
import net.szum123321.textile_backup.ConfigHandler;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
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.Statics;
|
||||
import net.szum123321.textile_backup.core.ActionInitiator;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
@ -36,6 +39,9 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RestoreHelper {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||
|
||||
public static Optional<RestoreableFile> findFileAndLockIfPresent(LocalDateTime backupTime, MinecraftServer server) {
|
||||
File root = Utilities.getBackupRootPath(Utilities.getLevelName(server));
|
||||
|
||||
@ -52,24 +58,24 @@ public class RestoreHelper {
|
||||
|
||||
public static AwaitThread create(RestoreContext ctx) {
|
||||
if(ctx.getInitiator() == ActionInitiator.Player)
|
||||
Statics.LOGGER.info("Backup restoration was initiated by: {}", ctx.getCommandSource().getName());
|
||||
log.info("Backup restoration was initiated by: {}", ctx.getCommandSource().getName());
|
||||
else
|
||||
Statics.LOGGER.info("Backup restoration was initiated form Server Console");
|
||||
log.info("Backup restoration was initiated form Server Console");
|
||||
|
||||
notifyPlayers(ctx);
|
||||
|
||||
return new AwaitThread(
|
||||
Statics.CONFIG.restoreDelay,
|
||||
config.get().restoreDelay,
|
||||
new RestoreBackupRunnable(ctx)
|
||||
);
|
||||
}
|
||||
|
||||
private static void notifyPlayers(RestoreContext ctx) {
|
||||
MutableText message = Statics.LOGGER.getPrefixText();
|
||||
MutableText message = log.getPrefixText();
|
||||
message.append(
|
||||
new LiteralText(
|
||||
"Warning! The server is going to shut down in " +
|
||||
Statics.CONFIG.restoreDelay +
|
||||
config.get().restoreDelay +
|
||||
" seconds!"
|
||||
).formatted(Formatting.WHITE)
|
||||
);
|
||||
@ -100,7 +106,7 @@ public class RestoreHelper {
|
||||
|
||||
public static class RestoreableFile implements Comparable<RestoreableFile> {
|
||||
private final File file;
|
||||
private final ConfigHandler.ArchiveFormat archiveFormat;
|
||||
private final ConfigPOJO.ArchiveFormat archiveFormat;
|
||||
private final LocalDateTime creationTime;
|
||||
private final String comment;
|
||||
|
||||
@ -131,7 +137,7 @@ public class RestoreHelper {
|
||||
return file;
|
||||
}
|
||||
|
||||
public ConfigHandler.ArchiveFormat getArchiveFormat() {
|
||||
public ConfigPOJO.ArchiveFormat getArchiveFormat() {
|
||||
return archiveFormat;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.restore.decompressors;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
@ -32,6 +33,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public class GenericTarDecompressor {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static void decompress(File input, File target) {
|
||||
Instant start = Instant.now();
|
||||
|
||||
@ -43,7 +46,7 @@ public class GenericTarDecompressor {
|
||||
|
||||
while ((entry = archiveInputStream.getNextTarEntry()) != null) {
|
||||
if(!archiveInputStream.canReadEntryData(entry)) {
|
||||
Statics.LOGGER.error("Something when wrong while trying to decompress {}", entry.getName());
|
||||
log.error("Something when wrong while trying to decompress {}", entry.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -55,22 +58,22 @@ public class GenericTarDecompressor {
|
||||
File parent = file.getParentFile();
|
||||
|
||||
if (!parent.isDirectory() && !parent.mkdirs()) {
|
||||
Statics.LOGGER.error("Failed to create {}", parent);
|
||||
log.error("Failed to create {}", parent);
|
||||
} else {
|
||||
try (OutputStream outputStream = Files.newOutputStream(file.toPath());
|
||||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
|
||||
IOUtils.copy(archiveInputStream, bufferedOutputStream);
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred while trying to decompress file: {}", file.getName(), e);
|
||||
log.error("An exception occurred while trying to decompress file: {}", file.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | CompressorException e) {
|
||||
Statics.LOGGER.error("An exception occurred! ", e);
|
||||
log.error("An exception occurred! ", e);
|
||||
}
|
||||
|
||||
Statics.LOGGER.info("Decompression took {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));
|
||||
log.info("Decompression took {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));
|
||||
}
|
||||
|
||||
private static InputStream getCompressorInputStream(InputStream inputStream) throws CompressorException {
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
package net.szum123321.textile_backup.core.restore.decompressors;
|
||||
|
||||
import net.szum123321.textile_backup.Statics;
|
||||
import net.szum123321.textile_backup.TextileBackup;
|
||||
import net.szum123321.textile_backup.TextileLogger;
|
||||
import net.szum123321.textile_backup.core.Utilities;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
@ -30,6 +31,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public class ZipDecompressor {
|
||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||
|
||||
public static void decompress(File inputFile, File target) {
|
||||
Instant start = Instant.now();
|
||||
|
||||
@ -40,7 +43,7 @@ public class ZipDecompressor {
|
||||
|
||||
while ((entry = zipInputStream.getNextZipEntry()) != null) {
|
||||
if(!zipInputStream.canReadEntryData(entry)){
|
||||
Statics.LOGGER.error("Something when wrong while trying to decompress {}", entry.getName());
|
||||
log.error("Something when wrong while trying to decompress {}", entry.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -52,21 +55,21 @@ public class ZipDecompressor {
|
||||
File parent = file.getParentFile();
|
||||
|
||||
if (!parent.isDirectory() && !parent.mkdirs()) {
|
||||
Statics.LOGGER.error("Failed to create {}", parent);
|
||||
log.error("Failed to create {}", parent);
|
||||
} else {
|
||||
try (OutputStream outputStream = Files.newOutputStream(file.toPath());
|
||||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
|
||||
IOUtils.copy(zipInputStream, bufferedOutputStream);
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred while trying to decompress file: {}", file.getName(), e);
|
||||
log.error("An exception occurred while trying to decompress file: {}", file.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Statics.LOGGER.error("An exception occurred! ", e);
|
||||
log.error("An exception occurred! ", e);
|
||||
}
|
||||
|
||||
Statics.LOGGER.info("Decompression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));
|
||||
log.info("Decompression took: {} seconds.", Utilities.formatDuration(Duration.between(start, Instant.now())));
|
||||
}
|
||||
}
|
||||
|
49
src/main/resources/assets/textile_backup/lang/en_us.json
Normal file
49
src/main/resources/assets/textile_backup/lang/en_us.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"text.autoconfig.textile_backup.title": "Textile Backup Configuration",
|
||||
|
||||
"text.autoconfig.textile_backup.category.default": "General",
|
||||
"text.autoconfig.textile_backup.category.Create": "Backup settings",
|
||||
"text.autoconfig.textile_backup.category.Restore": "Restore",
|
||||
"text.autoconfig.textile_backup.category.Manage": "Management",
|
||||
|
||||
"text.autoconfig.textile_backup.option.backupInterval": "Backup Interval",
|
||||
"text.autoconfig.textile_backup.option.backupInterval.@Tooltip": "AAAAAA",
|
||||
|
||||
"text.autoconfig.textile_backup.option.restoreDelay": "Restore Delay",
|
||||
|
||||
"text.autoconfig.textile_backup.option.doBackupsOnEmptyServer": "Do backups on empty server",
|
||||
|
||||
"text.autoconfig.textile_backup.option.shutdownBackup": "Make a backup on shutdown",
|
||||
|
||||
"text.autoconfig.textile_backup.option.backupOldWorlds": "Backup old worlds",
|
||||
|
||||
"text.autoconfig.textile_backup.option.perWorldBackup": "Use separate folders for different worlds",
|
||||
|
||||
"text.autoconfig.textile_backup.option.path": "Path to backup folder",
|
||||
|
||||
"text.autoconfig.textile_backup.option.fileBlacklist": "Blacklised files",
|
||||
|
||||
"text.autoconfig.textile_backup.option.deleteOldBackupAfterRestore": "Delete restored backup",
|
||||
|
||||
"text.autoconfig.textile_backup.option.backupsToKeep": "Number of backups to keep",
|
||||
|
||||
"text.autoconfig.textile_backup.option.maxAge": "Max age of backup",
|
||||
|
||||
"text.autoconfig.textile_backup.option.maxSize": "Max size of backup folder",
|
||||
|
||||
"text.autoconfig.textile_backup.option.compression": "Compression level (Zip only)",
|
||||
|
||||
"text.autoconfig.textile_backup.option.compressionCoreCountLimit": "Max number of cores used for compression",
|
||||
|
||||
"text.autoconfig.textile_backup.option.format": "Archive and compression format",
|
||||
|
||||
"text.autoconfig.textile_backup.option.permissionLevel": "Min permission level required to run any command",
|
||||
|
||||
"text.autoconfig.textile_backup.option.alwaysSingleplayerAllowed": "Always allow on sigle-player",
|
||||
|
||||
"text.autoconfig.textile_backup.option.playerWhitelist": "Admin Whitelist",
|
||||
|
||||
"text.autoconfig.textile_backup.option.playerBlacklist": "Admin Blacklist",
|
||||
|
||||
"text.autoconfig.textile_backup.option.dateTimeFormat": "Date&Time format"
|
||||
}
|
@ -27,6 +27,9 @@
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.szum123321.textile_backup.TextileBackup"
|
||||
],
|
||||
"modmenu": [
|
||||
"net.szum123321.textile_backup.client.ModMenuEntry"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
|
Loading…
Reference in New Issue
Block a user