diff --git a/gradle.properties b/gradle.properties index bc4bc52..f54aa06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,6 @@ loader_version=0.8.9+build.203 fabric_version=0.14.1+build.372-1.16 # Mod Properties -mod_version = 1.2.4 +mod_version = 1.3.0 maven_group = net.szum123321 archives_base_name = textile_backup \ No newline at end of file diff --git a/src/main/java/net/szum123321/textile_backup/ConfigHandler.java b/src/main/java/net/szum123321/textile_backup/ConfigHandler.java index bfc50ef..af600db 100644 --- a/src/main/java/net/szum123321/textile_backup/ConfigHandler.java +++ b/src/main/java/net/szum123321/textile_backup/ConfigHandler.java @@ -21,18 +21,21 @@ package net.szum123321.textile_backup; import blue.endless.jankson.Comment; import io.github.cottonmc.cotton.config.annotations.ConfigFile; +import java.time.format.DateTimeFormatter; import java.util.HashSet; +import java.util.Optional; import java.util.Set; @ConfigFile(name = TextileBackup.MOD_ID) public class ConfigHandler { - @Comment("\nTime between automatic backups in seconds\n") + @Comment("\nTime between automatic backups in seconds\n" + + "When set to 0 backups will not be performed automatically\n") public long backupInterval = 3600; @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") + @Comment("\nShould backup be made on server shutdown?\n") public boolean shutdownBackup = true; @Comment("\nA path to backup folder\n") @@ -84,10 +87,24 @@ public class ConfigHandler { public Set playerBlacklist = new HashSet<>(); @Comment("\nFormat of date&time used to name backup files.\n" + - "Remember not to use '#' symbol and any other character that is not allowed by your operating system such as:\n" + - "':', '\\', etc\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 = "dd.MM.yyyy_HH-mm-ss"; + public Optional 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"); + } + + return Optional.empty(); + } + public enum ArchiveFormat { ZIP(".zip"), GZIP(".tar.gz"), diff --git a/src/main/java/net/szum123321/textile_backup/TextileBackup.java b/src/main/java/net/szum123321/textile_backup/TextileBackup.java index d70999a..831a2df 100644 --- a/src/main/java/net/szum123321/textile_backup/TextileBackup.java +++ b/src/main/java/net/szum123321/textile_backup/TextileBackup.java @@ -23,28 +23,46 @@ import io.github.cottonmc.cotton.config.ConfigManager; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.server.command.ServerCommandSource; import net.szum123321.textile_backup.commands.BlacklistCommand; import net.szum123321.textile_backup.commands.CleanupCommand; import net.szum123321.textile_backup.commands.StartBackupCommand; import net.szum123321.textile_backup.commands.WhitelistCommand; +import net.szum123321.textile_backup.core.BackupScheduler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class TextileBackup implements ModInitializer { public static final String MOD_ID = "textile_backup"; public static final Logger LOGGER = LogManager.getFormatterLogger("Textile Backup"); public static ConfigHandler config; + public static final BackupScheduler scheduler = new BackupScheduler(); + public static final ExecutorService executorService = Executors.newSingleThreadExecutor(); + @Override public void onInitialize() { config = ConfigManager.loadConfig(ConfigHandler.class); - registerCommands(); - } + Optional errorMessage = config.sanitize(); + + if(errorMessage.isPresent()) { + LOGGER.fatal("TextileBackup config file has wrong settings! \n" + errorMessage.get()); + System.exit(1); + } + + if(TextileBackup.config.backupInterval > 0) + ServerTickEvents.END_SERVER_TICK.register(scheduler::tick); + + ServerLifecycleEvents.SERVER_STOPPED.register(ignored -> executorService.shutdown()); - private void registerCommands(){ CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register( LiteralArgumentBuilder.literal("backup") .requires((ctx) -> { @@ -53,7 +71,7 @@ public class TextileBackup implements ModInitializer { ctx.hasPermissionLevel(config.permissionLevel)) && !config.playerBlacklist.contains(ctx.getEntityOrThrow().getEntityName())) || (ctx.getMinecraftServer().isSinglePlayer() && - config.alwaysSingleplayerAllowed); + config.alwaysSingleplayerAllowed); } catch (Exception ignored) { //Command was called from server console. return true; } diff --git a/src/main/java/net/szum123321/textile_backup/commands/StartBackupCommand.java b/src/main/java/net/szum123321/textile_backup/commands/StartBackupCommand.java index ac9c83c..ed205e5 100644 --- a/src/main/java/net/szum123321/textile_backup/commands/StartBackupCommand.java +++ b/src/main/java/net/szum123321/textile_backup/commands/StartBackupCommand.java @@ -23,6 +23,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; +import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.core.BackupHelper; public class StartBackupCommand { @@ -34,13 +35,27 @@ public class StartBackupCommand { } private static int executeWithComment(CommandContext source) { - BackupHelper.create(source.getSource().getMinecraftServer(), source.getSource(), true, StringArgumentType.getString(source, "comment").replace("#", "")); + TextileBackup.executorService.submit( + BackupHelper.create( + source.getSource().getMinecraftServer(), + source.getSource(), + true, + StringArgumentType.getString(source, "comment").replace("#", "") + ) + ); return 1; } private static int execute(ServerCommandSource source){ - BackupHelper.create(source.getMinecraftServer(), source,true, null); + TextileBackup.executorService.submit( + BackupHelper.create( + source.getMinecraftServer(), + source, + true, + null + ) + ); return 1; } diff --git a/src/main/java/net/szum123321/textile_backup/core/BackupHelper.java b/src/main/java/net/szum123321/textile_backup/core/BackupHelper.java index 6d4efe8..7e63e30 100644 --- a/src/main/java/net/szum123321/textile_backup/core/BackupHelper.java +++ b/src/main/java/net/szum123321/textile_backup/core/BackupHelper.java @@ -32,7 +32,7 @@ import java.util.Comparator; import java.util.concurrent.atomic.AtomicInteger; public class BackupHelper { - public static Thread create(MinecraftServer server, ServerCommandSource ctx, boolean save, String comment) { + public static Runnable create(MinecraftServer server, ServerCommandSource ctx, boolean save, String comment) { LocalDateTime now = LocalDateTime.now(); StringBuilder builder = new StringBuilder(); @@ -53,11 +53,7 @@ public class BackupHelper { if (save) server.save(true, true, false); - Thread thread = new Thread(new MakeBackupThread(server, ctx, comment)); - - thread.start(); - - return thread; + return new MakeBackupRunnable(server, ctx, comment); } public static void executeFileLimit(ServerCommandSource ctx, String worldName) { diff --git a/src/main/java/net/szum123321/textile_backup/core/BackupScheduler.java b/src/main/java/net/szum123321/textile_backup/core/BackupScheduler.java new file mode 100644 index 0000000..0ae155d --- /dev/null +++ b/src/main/java/net/szum123321/textile_backup/core/BackupScheduler.java @@ -0,0 +1,39 @@ +package net.szum123321.textile_backup.core; + +import net.minecraft.server.MinecraftServer; +import net.szum123321.textile_backup.TextileBackup; + +import java.time.Instant; + +public class BackupScheduler { + private boolean scheduled; + private long nextBackup; + + public BackupScheduler() { + scheduled = false; + nextBackup = -1; + } + + public void tick(MinecraftServer server) { + long now = Instant.now().getEpochSecond(); + + if(TextileBackup.config.doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) { + if(scheduled) { + if(nextBackup <= now) { + TextileBackup.executorService.submit(BackupHelper.create(server, null, true, null)); + + nextBackup = now + TextileBackup.config.backupInterval; + } + } else { + nextBackup = now + TextileBackup.config.backupInterval; + scheduled = true; + } + } else if(!TextileBackup.config.doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) { + if(scheduled && nextBackup <= now) { + TextileBackup.executorService.submit(BackupHelper.create(server, null, true, null)); + + scheduled = false; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/net/szum123321/textile_backup/core/MakeBackupThread.java b/src/main/java/net/szum123321/textile_backup/core/MakeBackupRunnable.java similarity index 96% rename from src/main/java/net/szum123321/textile_backup/core/MakeBackupThread.java rename to src/main/java/net/szum123321/textile_backup/core/MakeBackupRunnable.java index 8e81e1f..51621d8 100644 --- a/src/main/java/net/szum123321/textile_backup/core/MakeBackupThread.java +++ b/src/main/java/net/szum123321/textile_backup/core/MakeBackupRunnable.java @@ -34,12 +34,12 @@ import java.io.File; import java.io.IOException; import java.time.LocalDateTime; -public class MakeBackupThread implements Runnable { +public class MakeBackupRunnable implements Runnable { private final MinecraftServer server; private final ServerCommandSource ctx; private final String comment; - public MakeBackupThread(MinecraftServer server, ServerCommandSource ctx, String comment){ + public MakeBackupRunnable(MinecraftServer server, ServerCommandSource ctx, String comment){ this.server = server; this.ctx = ctx; this.comment = comment; diff --git a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java b/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java index 6c48fe5..5b4ce05 100644 --- a/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java +++ b/src/main/java/net/szum123321/textile_backup/mixin/MinecraftServerMixin.java @@ -19,45 +19,18 @@ package net.szum123321.textile_backup.mixin; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.PlayerManager; import net.szum123321.textile_backup.TextileBackup; import net.szum123321.textile_backup.core.BackupHelper; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.function.BooleanSupplier; - @Mixin(MinecraftServer.class) public abstract class MinecraftServerMixin { - @Shadow private long timeReference; - - @Shadow public abstract PlayerManager getPlayerManager(); - - private long lastBackup = 0; - - @Inject(method = "tick", at = @At("HEAD")) - public void tick(BooleanSupplier shouldKeepTicking, CallbackInfo ci){ - if(timeReference - lastBackup >= TextileBackup.config.backupInterval * 1000){ - if(getPlayerManager().getCurrentPlayerCount() == 0 && !TextileBackup.config.doBackupsOnEmptyServer) - return; - - lastBackup = timeReference; - - BackupHelper.create((MinecraftServer)(Object)this, null, true, null); - } - } - @Inject(method = "shutdown", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/server/MinecraftServer;save(ZZZ)Z")) public void onShutdown(CallbackInfo ci){ - if(TextileBackup.config.shutdownBackup) { - try { - BackupHelper.create((MinecraftServer) (Object) this, null, false, "shutdown").join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } + if(TextileBackup.config.shutdownBackup) + TextileBackup.executorService.submit(BackupHelper.create((MinecraftServer) (Object) this, null, false, "shutdown")); } }