diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModContainer.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModContainer.java index d3b28f0..522664c 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModContainer.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModContainer.java @@ -16,6 +16,10 @@ @Getter public class ModContainer { private ModVersion modVersion; + private ModEnvType envType; + + private String originalStoragePath; + private String levelId = ""; private CommandDispatcher dispatcher; private Path configPath; diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModEnvType.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModEnvType.java new file mode 100644 index 0000000..ba96d76 --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/ModEnvType.java @@ -0,0 +1,6 @@ +package io.github.skydynamic.quickbakcupmulti; + +public enum ModEnvType { + CLIENT, + SERVER +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/QuickbakcupmultiReforged.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/QuickbakcupmultiReforged.java index aa0f976..119fb2d 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/QuickbakcupmultiReforged.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/QuickbakcupmultiReforged.java @@ -4,6 +4,7 @@ import io.github.skydynamic.increment.storage.lib.utils.StorageManager; import io.github.skydynamic.quickbakcupmulti.command.ModCommand; import io.github.skydynamic.quickbakcupmulti.config.ModConfig; +import io.github.skydynamic.quickbakcupmulti.database.DatabaseManager; import io.github.skydynamic.quickbakcupmulti.schedule.quartz.DisableQuartzInfoLogger; import io.github.skydynamic.quickbakcupmulti.translate.Translate; import io.github.skydynamic.quickbakcupmulti.utils.UpdateChecker; @@ -15,6 +16,7 @@ import java.io.File; import java.text.SimpleDateFormat; +import java.util.UUID; public final class QuickbakcupmultiReforged { public static final String MOD_ID = "quickbakcupmulti_reforged"; @@ -40,7 +42,9 @@ public static void init(ModContainer container) { modConfig.save(); modContainer.setPermissionManager(new PermissionManager()); - new UpdateChecker().start(); + if (modConfig.isCheckUpdate()) { + new UpdateChecker().start(); + } // Initialize Translate Translate.handleResourceReload(modConfig.getLang()); @@ -64,4 +68,21 @@ public static String formatTimestamp(long timestamp) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(timestamp); } + + public static void setNewDataBase(String collectionName) { + QuickbakcupmultiReforged.getModContainer().setOriginalStoragePath(QuickbakcupmultiReforged.getModConfig().getStoragePath()); + + String appendFolder = (QuickbakcupmultiReforged.getModContainer().getEnvType() == ModEnvType.CLIENT) ? "/" + collectionName : ""; + DatabaseManager databaseManager = new DatabaseManager( + "QuickBakcupMulti", + QuickbakcupmultiReforged.getModConfig().getStoragePath(), + UUID.nameUUIDFromBytes(collectionName.getBytes()) + ); + + ModConfig modTempConfig = QuickbakcupmultiReforged.getModConfig().copy(); + + modTempConfig.setStoragePath(QuickbakcupmultiReforged.getModConfig().getStoragePath() + appendFolder); + QuickbakcupmultiReforged.setDatabase(new Database(databaseManager)); + QuickbakcupmultiReforged.setManager(new StorageManager(QuickbakcupmultiReforged.getDatabase(), modTempConfig)); + } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/client/screen/RestoreScreen.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/client/screen/RestoreScreen.java new file mode 100644 index 0000000..833e7ca --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/client/screen/RestoreScreen.java @@ -0,0 +1,58 @@ +package io.github.skydynamic.quickbakcupmulti.client.screen; + +import io.github.skydynamic.quickbakcupmulti.translate.Translate; +import lombok.Setter; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class RestoreScreen extends Screen { + private final Button cancelButton; + @Setter + private String state = Translate.tr("quickbackupmulti.screen.restore_screen.title"); + @Setter + private String progress = "0%"; + + public RestoreScreen(Button.OnPress onCancelButtonPress) { + super(Component.nullToEmpty(Translate.tr("quickbackupmulti.screen.restore_screen.title"))); + cancelButton = Button.builder(Component.nullToEmpty(Translate.tr("quickbackupmulti.screen.restore_screen.cancel_button")), onCancelButtonPress).build(); + } + + @Override + protected void init() { + cancelButton.setPosition(this.width / 2 - cancelButton.getWidth() / 2, this.height / 2 + 40); + + this.addRenderableWidget(this.cancelButton); + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) { + super.render(guiGraphics, mouseX, mouseY, delta); + guiGraphics.drawCenteredString(font, Component.nullToEmpty(this.state), this.width / 2, this.height / 2, 0xFFFFFF); + guiGraphics.drawCenteredString( + font, + Component.nullToEmpty(Translate.tr("quickbackupmulti.screen.restore_screen.progress") + this.progress), + this.width / 2, + this.height / 2 + 20, + 0xFFFFFF + ); + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } + + @Override + protected boolean shouldNarrateNavigation() { + return false; + } + + @Override + public void renderBackground(GuiGraphics guiGraphics, int i, int j, float f) { + this.renderPanorama(guiGraphics, f); + this.renderBlurredBackground(f); + this.renderMenuBackground(guiGraphics); + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/ModCommand.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/ModCommand.java index 3a63522..45db5fe 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/ModCommand.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/ModCommand.java @@ -1,6 +1,8 @@ package io.github.skydynamic.quickbakcupmulti.command; import com.mojang.brigadier.CommandDispatcher; +import io.github.skydynamic.quickbakcupmulti.ModEnvType; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; import io.github.skydynamic.quickbakcupmulti.command.settings.SettingCommand; import lombok.Getter; import net.minecraft.commands.CommandSourceStack; @@ -49,4 +51,12 @@ public static void register(CommandDispatcher dispatcher) { .then(ShowCommand.cmd) ); } + + public static boolean serverOnly(CommandSourceStack stack) { + return QuickbakcupmultiReforged.getModContainer().getEnvType() == ModEnvType.SERVER; + } + + public static boolean clientOnly(CommandSourceStack stack) { + return QuickbakcupmultiReforged.getModContainer().getEnvType() == ModEnvType.CLIENT; + } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/RestoreCommand.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/RestoreCommand.java index c7bb851..d53ef9d 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/RestoreCommand.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/RestoreCommand.java @@ -3,6 +3,7 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.restore.RestoreTimer; import io.github.skydynamic.quickbakcupmulti.utils.permission.PermissionManager; import io.github.skydynamic.quickbakcupmulti.utils.permission.PermissionType; import lombok.Getter; @@ -17,7 +18,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -50,20 +50,6 @@ public class RestoreCommand { .requires(it -> PermissionManager.hasPermission(it, 4, PermissionType.ADMIN)) .executes(it -> cancelRestore(it.getSource())); - private static class RestoreThread extends TimerTask { - private final Runnable executor; - - public RestoreThread(Runnable executor) { - this.executor = executor; - } - - @Override - public void run() { - ModCommand.getLogger().info("Restore thread started..."); - executor.run(); - } - } - @Getter private static final ConcurrentHashMap> restoreDataMap = new ConcurrentHashMap<>(); @@ -79,8 +65,8 @@ private static int restoreBackup(CommandSourceStack commandSource, String name) synchronized (restoreDataMap) { restoreDataMap.put("QBM", restoreMap); commandSource.sendSystemMessage(Component.nullToEmpty(tr("quickbackupmulti.restore.confirm_hint"))); - return 1; } + return 1; } private static void executeRestore(CommandSourceStack commandSource) { @@ -122,14 +108,7 @@ private static void executeRestore(CommandSourceStack commandSource) { countdown.shutdown(); } }, 0, 1, TimeUnit.SECONDS); - timer.schedule(new RestoreThread(() -> { - restoreDataMap.clear(); - for (ServerPlayer player : players) { - player.connection.disconnect(Component.literal("Server restore backup")); - } - QuickbakcupmultiReforged.getModContainer().setRestoringBackup(true); - QuickbakcupmultiReforged.getServerManager().stopServer(); - }), 10000); + timer.schedule(new RestoreTimer(QuickbakcupmultiReforged.getModContainer().getEnvType(), players), 10000); } else { commandSource.sendSystemMessage(Component.nullToEmpty(tr("quickbackupmulti.confirm_restore.nothing_to_confirm"))); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/AutoReJoinSettingCommand.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/AutoReJoinSettingCommand.java new file mode 100644 index 0000000..210307d --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/AutoReJoinSettingCommand.java @@ -0,0 +1,24 @@ +package io.github.skydynamic.quickbakcupmulti.command.settings; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.command.ModCommand; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +import static io.github.skydynamic.quickbakcupmulti.translate.Translate.tr; + +public class AutoReJoinSettingCommand { + public static final LiteralArgumentBuilder cmd = Commands.literal("auto-rejoin") + .requires(ModCommand::clientOnly) + .executes(it -> execute(it.getSource())); + + private static int execute(CommandSourceStack source) { + QuickbakcupmultiReforged.getModConfig().setClientAutoReJoinWorld(!QuickbakcupmultiReforged.getModConfig().isClientAutoReJoinWorld()); + source.sendSystemMessage( + Component.nullToEmpty(tr("quickbackupmulti.rejoin.switch", QuickbakcupmultiReforged.getModConfig().isClientAutoReJoinWorld())) + ); + return 0; + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/RestartModeSettingCommand.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/RestartModeSettingCommand.java index a24e511..10b3545 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/RestartModeSettingCommand.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/RestartModeSettingCommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.command.ModCommand; import io.github.skydynamic.quickbakcupmulti.config.ModConfig; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -11,6 +12,7 @@ public class RestartModeSettingCommand { public static final LiteralArgumentBuilder cmd = Commands.literal("auto-restart-mode") + .requires(ModCommand::serverOnly) .then(getCmdTree()); public static LiteralArgumentBuilder getCmdTree() { diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/SettingCommand.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/SettingCommand.java index 242ff5e..b8d235a 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/SettingCommand.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/command/settings/SettingCommand.java @@ -10,5 +10,6 @@ public class SettingCommand { public static final LiteralArgumentBuilder cmd = Commands.literal("setting") .requires(it -> PermissionManager.hasPermission(it, 4, PermissionType.ADMIN)) .then(LangSettingCommand.cmd) - .then(RestartModeSettingCommand.cmd); + .then(RestartModeSettingCommand.cmd) + .then(AutoReJoinSettingCommand.cmd); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/DatabaseConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/DatabaseConfig.java index 145ff5e..b777237 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/DatabaseConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/DatabaseConfig.java @@ -5,6 +5,5 @@ @SuppressWarnings("FieldMayBeFinal") @Getter public class DatabaseConfig { - private ScheduleConfig backup = new ScheduleConfig(); - + public ScheduleConfig backup = new ScheduleConfig(); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ModConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ModConfig.java index f3d3bec..22856bb 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ModConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ModConfig.java @@ -53,6 +53,11 @@ public boolean save() { } public boolean load() { + if (path == null) { + logger.error("Config Path is null"); + return false; + } + if (!Files.exists(path)) { return save(); } @@ -66,6 +71,57 @@ public boolean load() { return true; } + public ModConfig copy() { + ModConfig newConfig = new ModConfig(this.path); + ConfigStorage newStorage = new ConfigStorage(); + + newStorage.checkUpdate = this.config.checkUpdate; + newStorage.lang = this.config.lang; + newStorage.maxScheduleBackup = this.config.maxScheduleBackup; + newStorage.autoRestartMode = this.config.autoRestartMode; + newStorage.clientAutoReJoinWorld = this.config.clientAutoReJoinWorld; + newStorage.storagePath = this.config.storagePath; + newStorage.cacheDatabase = this.config.cacheDatabase; + + newStorage.ignoredFiles = new ArrayList<>(this.config.ignoredFiles); + newStorage.ignoredFolders = new ArrayList<>(this.config.ignoredFolders); + + newStorage.scheduleBackup = new ScheduleBackupConfig(); + newStorage.scheduleBackup.enabled = this.config.scheduleBackup.enabled; + newStorage.scheduleBackup.interval = this.config.scheduleBackup.interval; + newStorage.scheduleBackup.crontab = this.config.scheduleBackup.crontab; + newStorage.scheduleBackup.resetTimerOnBackup = this.config.scheduleBackup.resetTimerOnBackup; + newStorage.scheduleBackup.requireOnlinePlayers = this.config.scheduleBackup.requireOnlinePlayers; + newStorage.scheduleBackup.requireOnlinePlayersIgnoreCarpetFakePlayer = this.config.scheduleBackup.requireOnlinePlayersIgnoreCarpetFakePlayer; + newStorage.scheduleBackup.requireOnlinePlayersBlacklist = this.config.scheduleBackup.requireOnlinePlayersBlacklist; + + newStorage.prune = new PruneScheduleConfig(); + newStorage.prune.enabled = this.config.prune.enabled; + newStorage.prune.interval = this.config.prune.interval; + newStorage.prune.crontab = this.config.prune.crontab; + newStorage.prune.timezoneOverride = this.config.prune.timezoneOverride; + + newStorage.prune.regularBackup = new PbsConfig(); + newStorage.prune.regularBackup.enabled = this.config.prune.regularBackup.enabled; + newStorage.prune.regularBackup.maxAmount = this.config.prune.regularBackup.maxAmount; + newStorage.prune.regularBackup.maxLifeTime = this.config.prune.regularBackup.maxLifeTime; + newStorage.prune.regularBackup.last = this.config.prune.regularBackup.last; + newStorage.prune.regularBackup.hour = this.config.prune.regularBackup.hour; + newStorage.prune.regularBackup.day = this.config.prune.regularBackup.day; + newStorage.prune.regularBackup.week = this.config.prune.regularBackup.week; + newStorage.prune.regularBackup.month = this.config.prune.regularBackup.month; + newStorage.prune.regularBackup.year = this.config.prune.regularBackup.year; + + newStorage.database = new DatabaseConfig(); + newStorage.database.backup = new ScheduleConfig(); + newStorage.database.backup.enabled = this.config.database.backup.enabled; + newStorage.database.backup.interval = this.config.database.backup.interval; + newStorage.database.backup.crontab = this.config.database.backup.crontab; + + newConfig.setConfig(newStorage); + return newConfig; + } + public boolean isCheckUpdate() { return config.checkUpdate; } @@ -88,6 +144,7 @@ public String getLang() { public void setLang(String lang) { config.lang = lang; + save(); } public int getMaxScheduleBackup() { @@ -100,6 +157,16 @@ public AutoRestartMode getAutoRestartMode() { public void setAutoRestartMode(AutoRestartMode autoRestartMode) { config.autoRestartMode = autoRestartMode; + save(); + } + + public boolean isClientAutoReJoinWorld() { + return config.clientAutoReJoinWorld; + } + + public void setClientAutoReJoinWorld(boolean clientAutoReJoinWorld) { + config.clientAutoReJoinWorld = clientAutoReJoinWorld; + save(); } public boolean isCacheDatabase() { @@ -123,6 +190,10 @@ public DatabaseConfig getDatabaseConfig() { return config.storagePath; } + public void setStoragePath(String storagePath) { + config.storagePath = storagePath; + } + public enum AutoRestartMode { DISABLE, DEFAULT, @@ -140,6 +211,7 @@ public static class ConfigStorage { private int maxScheduleBackup = 10; private AutoRestartMode autoRestartMode = AutoRestartMode.DEFAULT; + private boolean clientAutoReJoinWorld = true; private String storagePath = "./QuickBackupMulti"; private boolean cacheDatabase = false; @@ -159,6 +231,7 @@ public String toString() { ", lang='" + lang + '\'' + ", maxScheduleBackup=" + maxScheduleBackup + ", autoRestartMode=" + autoRestartMode + + ", clientAutoReJoinWorld=" + clientAutoReJoinWorld + ", storagePath='" + storagePath + '\'' + ", cacheDatabase=" + cacheDatabase + ", scheduleBackup=" + scheduleBackup + diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PbsConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PbsConfig.java index ac92fbc..6b04a27 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PbsConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PbsConfig.java @@ -5,13 +5,13 @@ @SuppressWarnings("FieldMayBeFinal") @Getter public class PbsConfig { - private boolean enabled = false; - private Integer maxAmount = 10; - private String maxLifeTime = "0s"; - private int last = -1; - private int hour = 0; - private int day = 0; - private int week = 0; - private int month = 1; - private int year = 0; + public boolean enabled = false; + public Integer maxAmount = 10; + public String maxLifeTime = "0s"; + public int last = -1; + public int hour = 0; + public int day = 0; + public int week = 0; + public int month = 1; + public int year = 0; } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PruneScheduleConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PruneScheduleConfig.java index 29d9625..7a4f012 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PruneScheduleConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/PruneScheduleConfig.java @@ -5,6 +5,7 @@ @SuppressWarnings("FieldMayBeFinal") @Getter public class PruneScheduleConfig extends ScheduleConfig { - private String timezoneOverride = null; - private PbsConfig regularBackup = new PbsConfig(); + public String timezoneOverride = null; + public PbsConfig regularBackup = new PbsConfig(); + public ScheduleConfig temporaryBackup = new ScheduleConfig(); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleBackupConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleBackupConfig.java index d8d7cb0..03a3c9f 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleBackupConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleBackupConfig.java @@ -7,8 +7,8 @@ @SuppressWarnings("FieldMayBeFinal") @Getter public class ScheduleBackupConfig extends ScheduleConfig { - private boolean resetTimerOnBackup = true; - private boolean requireOnlinePlayers = false; - private boolean requireOnlinePlayersIgnoreCarpetFakePlayer = true; - private List requireOnlinePlayersBlacklist = List.of(); + public boolean resetTimerOnBackup = true; + public boolean requireOnlinePlayers = false; + public boolean requireOnlinePlayersIgnoreCarpetFakePlayer = true; + public List requireOnlinePlayersBlacklist = List.of(); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleConfig.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleConfig.java index d4cf0d7..641bef2 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleConfig.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/config/ScheduleConfig.java @@ -2,7 +2,6 @@ public class ScheduleConfig { public boolean enabled; - public Integer interval = 7200; + public String interval = "3h"; public String crontab = null; - public String jitter = "1m"; } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnLoadedWorldHandler.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnLoadedWorldHandler.java index abe69f5..ac86758 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnLoadedWorldHandler.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnLoadedWorldHandler.java @@ -8,6 +8,7 @@ import io.github.skydynamic.quickbakcupmulti.schedule.runnables.DefaultDatabaseBackupRunnable; import io.github.skydynamic.quickbakcupmulti.schedule.runnables.DefaultPruneRunnable; import io.github.skydynamic.quickbakcupmulti.schedule.runnables.DefaultScheduleBackupRunnable; +import io.github.skydynamic.quickbakcupmulti.utils.DurationUtils; public class OnLoadedWorldHandler { public static void handler() { @@ -16,9 +17,10 @@ public static void handler() { if (databaseBackupConfig.enabled) { Runnable databaseBackupRunnable = new DefaultDatabaseBackupRunnable(); if (databaseBackupConfig.interval != null) { - ScheduleManager.registerSchedule("databaseSchedule", databaseBackupConfig.interval, databaseBackupConfig.jitter, databaseBackupRunnable); + int interval = (int) DurationUtils.parseDurationToSeconds(databaseBackupConfig.interval); + ScheduleManager.registerSchedule("databaseSchedule", interval, databaseBackupRunnable); } else if (databaseBackupConfig.crontab != null) { - ScheduleManager.registerSchedule("databaseSchedule", databaseBackupConfig.crontab, databaseBackupConfig.jitter, databaseBackupRunnable); + ScheduleManager.registerSchedule("databaseSchedule", databaseBackupConfig.crontab, databaseBackupRunnable); } } @@ -27,20 +29,39 @@ public static void handler() { if (scheduleBackupConfig.enabled) { Runnable scheduleBackupRunnable = new DefaultScheduleBackupRunnable(); if (scheduleBackupConfig.interval != null) { - ScheduleManager.registerSchedule("scheduleBackup", scheduleBackupConfig.interval, scheduleBackupConfig.jitter, scheduleBackupRunnable); + int interval = (int) DurationUtils.parseDurationToSeconds(scheduleBackupConfig.interval); + ScheduleManager.registerSchedule("scheduleBackup", databaseBackupConfig.interval, scheduleBackupRunnable); } else if (scheduleBackupConfig.crontab != null) { - ScheduleManager.registerSchedule("scheduleBackup", scheduleBackupConfig.crontab, scheduleBackupConfig.jitter, scheduleBackupRunnable); + ScheduleManager.registerSchedule("scheduleBackup", scheduleBackupConfig.crontab, scheduleBackupRunnable); } } // Prune schedule PruneScheduleConfig pruneScheduleConfig = QuickbakcupmultiReforged.getModConfig().getPruneScheduleConfig(); if (pruneScheduleConfig.enabled) { - Runnable pruneRunnable = new DefaultPruneRunnable(); + Runnable pruneRunnable = DefaultPruneRunnable.PRUNE_REGULAR_BACKUP_RUNNABLE; if (pruneScheduleConfig.interval != null) { - ScheduleManager.registerSchedule("pruneSchedule", pruneScheduleConfig.interval, pruneScheduleConfig.jitter, pruneRunnable); + int interval = (int) DurationUtils.parseDurationToSeconds(pruneScheduleConfig.interval); + ScheduleManager.registerSchedule("pruneSchedule", interval, pruneRunnable); } else if (pruneScheduleConfig.crontab != null) { - ScheduleManager.registerSchedule("pruneSchedule", pruneScheduleConfig.crontab, pruneScheduleConfig.jitter, pruneRunnable); + ScheduleManager.registerSchedule("pruneSchedule", pruneScheduleConfig.crontab, pruneRunnable); + } + + // Prune temporary backup + if (pruneScheduleConfig.getTemporaryBackup().enabled) { + Runnable pruneTemporaryBackupRunnable = DefaultPruneRunnable.PRUNE_TEMPORARY_BACKUP_RUNNABLE; + if (pruneScheduleConfig.getTemporaryBackup().interval != null) { + int interval = (int) DurationUtils.parseDurationToSeconds(pruneScheduleConfig.getTemporaryBackup().interval); + ScheduleManager.registerSchedule( + "pruneTemporaryBackupSchedule", + interval, + pruneTemporaryBackupRunnable); + } else if (pruneScheduleConfig.getTemporaryBackup().crontab != null) { + ScheduleManager.registerSchedule( + "pruneTemporaryBackupSchedule", + pruneScheduleConfig.getTemporaryBackup().crontab, + pruneTemporaryBackupRunnable); + } } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnServerStoppedHandler.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnServerStoppedHandler.java index 31e4159..1b8649a 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnServerStoppedHandler.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/event/OnServerStoppedHandler.java @@ -1,23 +1,27 @@ package io.github.skydynamic.quickbakcupmulti.event; +import io.github.skydynamic.quickbakcupmulti.ModEnvType; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; import io.github.skydynamic.quickbakcupmulti.schedule.ScheduleManager; import io.github.skydynamic.quickbakcupmulti.utils.BackupManager; public class OnServerStoppedHandler { public static void handle() { + ScheduleManager.clearAllSchedule(); if (QuickbakcupmultiReforged.getModContainer().isRestoringBackup()) { - BackupManager.restoreBackup(QuickbakcupmultiReforged.getModContainer().getCurrentSelectionBackup()); - QuickbakcupmultiReforged.getModContainer().setRestoringBackup(false); - switch (QuickbakcupmultiReforged.getModConfig().getAutoRestartMode()) { - case DISABLE -> { + if (QuickbakcupmultiReforged.getModContainer().getEnvType() == ModEnvType.SERVER) { + BackupManager.makeTempBackup(); + BackupManager.restoreBackup(QuickbakcupmultiReforged.getModContainer().getCurrentSelectionBackup()); + QuickbakcupmultiReforged.getModContainer().setRestoringBackup(false); + switch (QuickbakcupmultiReforged.getModConfig().getAutoRestartMode()) { + case DISABLE -> { + } + case DEFAULT -> QuickbakcupmultiReforged.getServerManager().startServer(); + case MCSM -> new Thread(() -> System.exit(1)).start(); } - case DEFAULT -> QuickbakcupmultiReforged.getServerManager().startServer(); - case MCSM -> new Thread(() -> System.exit(1)).start(); } } else { QuickbakcupmultiReforged.getDatabase().closeDatabase(); - ScheduleManager.clearAllSchedule(); } } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/exception/RestoreCancelException.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/exception/RestoreCancelException.java new file mode 100644 index 0000000..499dbc3 --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/exception/RestoreCancelException.java @@ -0,0 +1,7 @@ +package io.github.skydynamic.quickbakcupmulti.exception; + +public class RestoreCancelException extends RuntimeException { + public RestoreCancelException(String message) { + super(message); + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinMinecraftServer.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinDedicatedServer.java similarity index 71% rename from common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinMinecraftServer.java rename to common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinDedicatedServer.java index 8e9a34e..c0bebe3 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinMinecraftServer.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/MixinDedicatedServer.java @@ -1,11 +1,8 @@ package io.github.skydynamic.quickbakcupmulti.mixin; import com.mojang.datafixers.DataFixer; -import io.github.skydynamic.increment.storage.lib.database.Database; -import io.github.skydynamic.increment.storage.lib.utils.StorageManager; import io.github.skydynamic.quickbakcupmulti.DatabaseCache; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; -import io.github.skydynamic.quickbakcupmulti.database.DatabaseManager; import io.github.skydynamic.quickbakcupmulti.event.OnLoadedWorldHandler; import net.minecraft.server.MinecraftServer; import net.minecraft.server.Services; @@ -21,11 +18,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.net.Proxy; -import java.util.UUID; @Mixin(DedicatedServer.class) -public abstract class MixinMinecraftServer extends MinecraftServer { - public MixinMinecraftServer( +public abstract class MixinDedicatedServer extends MinecraftServer { + public MixinDedicatedServer( Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, @@ -43,14 +39,8 @@ public MixinMinecraftServer( ) ) private void onLoadLevel(CallbackInfoReturnable cir) { - DatabaseManager databaseManager = new DatabaseManager( - "QuickBakcupMulti", - QuickbakcupmultiReforged.getModConfig().getStoragePath(), - UUID.nameUUIDFromBytes("server".getBytes()) - ); + QuickbakcupmultiReforged.setNewDataBase("server"); QuickbakcupmultiReforged.getModContainer().setCurrentSavePath(this.getWorldPath(LevelResource.ROOT)); - QuickbakcupmultiReforged.setDatabase(new Database(databaseManager)); - QuickbakcupmultiReforged.setManager(new StorageManager(QuickbakcupmultiReforged.getDatabase(), QuickbakcupmultiReforged.getModConfig())); if (QuickbakcupmultiReforged.getModConfig().isCacheDatabase()) { DatabaseCache.updateStorageInfoCaches(); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinClientPacketListener.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinClientPacketListener.java deleted file mode 100644 index 5d9ba4f..0000000 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinClientPacketListener.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.skydynamic.quickbakcupmulti.mixin.client; - -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundLoginPacket; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(ClientPacketListener.class) -public class MixinClientPacketListener { - @Inject( - method = "handleLogin", - at = @At("TAIL") - ) - private void showWarning(ClientboundLoginPacket packet, CallbackInfo ci) { - LocalPlayer player = Minecraft.getInstance().player; - if (player != null) { - player.displayClientMessage( - Component.literal("QuickbackupmultiReforged mod is not supporting clients now!") - .withStyle(ChatFormatting.RED), false - ); - } - } -} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinIntegratedServer.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinIntegratedServer.java new file mode 100644 index 0000000..9c686bf --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinIntegratedServer.java @@ -0,0 +1,42 @@ +package io.github.skydynamic.quickbakcupmulti.mixin.client; + +import com.mojang.datafixers.DataFixer; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.event.OnLoadedWorldHandler; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.Services; +import net.minecraft.server.WorldStem; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.net.Proxy; + +@Mixin(net.minecraft.client.server.IntegratedServer.class) +public abstract class MixinIntegratedServer extends MinecraftServer { + public MixinIntegratedServer( + Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, + PackRepository packRepository, WorldStem worldStem, + Proxy proxy, DataFixer dataFixer, Services services, + ChunkProgressListenerFactory chunkProgressListenerFactory + ) { + super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory); + } + + @Inject( + method = "initServer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/server/IntegratedServer;loadLevel()V", + shift = At.Shift.AFTER + ) + ) + private void onLoadLevel(CallbackInfoReturnable cir) { + OnLoadedWorldHandler.handler(); + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldOpenFlows.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldOpenFlows.java new file mode 100644 index 0000000..1b0e2cf --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldOpenFlows.java @@ -0,0 +1,46 @@ +package io.github.skydynamic.quickbakcupmulti.mixin.client; + +import io.github.skydynamic.quickbakcupmulti.DatabaseCache; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows; +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.ReloadableServerResources; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.WorldData; +import org.spongepowered.asm.mixin.Final; +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; + +@Mixin(WorldOpenFlows.class) +public class MixinWorldOpenFlows { + @Final + @Shadow + private Minecraft minecraft; + + @Inject( + method = "createLevelFromExistingSettings", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/packs/repository/ServerPacksSource;createPackRepository(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/server/packs/repository/PackRepository;" + ) + ) + private void onCreateLevelFromExistingSettings$createPackRepository( + LevelStorageSource.LevelStorageAccess levelStorageAccess, ReloadableServerResources reloadableServerResources, + LayeredRegistryAccess layeredRegistryAccess, WorldData worldData, + CallbackInfo ci + ) { + String worldName = levelStorageAccess.getLevelId(); + + QuickbakcupmultiReforged.getModContainer().setLevelId(worldName); + QuickbakcupmultiReforged.setNewDataBase(worldName); + QuickbakcupmultiReforged.getModContainer().setCurrentSavePath(this.minecraft.getLevelSource().getLevelPath(worldName)); + if (QuickbakcupmultiReforged.getModConfig().isCacheDatabase()) { + DatabaseCache.updateStorageInfoCaches(); + } + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldSelectionListWorldListEntry.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldSelectionListWorldListEntry.java new file mode 100644 index 0000000..da463c8 --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/mixin/client/MixinWorldSelectionListWorldListEntry.java @@ -0,0 +1,49 @@ +package io.github.skydynamic.quickbakcupmulti.mixin.client; + +import io.github.skydynamic.quickbakcupmulti.DatabaseCache; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.utils.BackupManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.worldselection.WorldSelectionList; +import net.minecraft.world.level.storage.LevelSummary; +import org.spongepowered.asm.mixin.Final; +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; + +@Mixin(WorldSelectionList.WorldListEntry.class) +public class MixinWorldSelectionListWorldListEntry { + @Shadow + @Final + LevelSummary summary; + + @Shadow + @Final + private Minecraft minecraft; + + @Inject( + method = "doDeleteWorld", + at = @At("RETURN") + ) + private void onDeleteWorld(CallbackInfo ci) { + String worldName = this.summary.getLevelId(); + BackupManager.deleteWorld(worldName); + } + + @Inject( + method = "joinWorld", + at = @At("RETURN") + ) + private void onJoinWorld(CallbackInfo ci) { + String worldName = this.summary.getLevelId(); + + QuickbakcupmultiReforged.getModContainer().setLevelId(worldName); + QuickbakcupmultiReforged.setNewDataBase(worldName); + QuickbakcupmultiReforged.getModContainer().setCurrentSavePath(this.minecraft.getLevelSource().getLevelPath(worldName)); + if (QuickbakcupmultiReforged.getModConfig().isCacheDatabase()) { + DatabaseCache.updateStorageInfoCaches(); + } + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/ClientRestoreDelegate.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/ClientRestoreDelegate.java new file mode 100644 index 0000000..f7a46ba --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/ClientRestoreDelegate.java @@ -0,0 +1,111 @@ +package io.github.skydynamic.quickbakcupmulti.restore; + +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.client.screen.RestoreScreen; +import io.github.skydynamic.quickbakcupmulti.translate.Translate; +import io.github.skydynamic.quickbakcupmulti.utils.BackupManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ClientRestoreDelegate { + private final RestoreScreen screen = new RestoreScreen(this::cancel); + private CompletableFuture restoreFuture = null; + private final AtomicBoolean isCancelled = new AtomicBoolean(false); + + protected final Minecraft minecraftClient = Minecraft.getInstance(); + String levelId = QuickbakcupmultiReforged.getModContainer().getLevelId(); + + public void run() { + long startTime = System.currentTimeMillis(); + minecraftClient.executeBlocking(() -> { + minecraftClient.level.disconnect(); + minecraftClient.disconnect(screen); + }); + + restoreFuture = CompletableFuture.runAsync(() -> { + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.make_temp_backup")); + BackupManager.makeTempBackup(); + screen.setProgress("5%"); + + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.delete_origin_save")); + deleteWorld(); + screen.setProgress("10%"); + + if (isCancelled.get()) { + handleCancellation(); + return; + } + + BackupManager.RestoreExtraRunnable extraRunnable = (totalProgress, currentProgress) -> { + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.restoring_backup")); + int progress = (int) ((currentProgress / (double) totalProgress) * 0.9 * 100); + screen.setProgress(progress + "%"); + }; + BackupManager.restoreBackup(QuickbakcupmultiReforged.getModContainer().getCurrentSelectionBackup(), extraRunnable); + + if (isCancelled.get()) { + handleCancellation(); + return; + } + + // Restore finish and rejoin the world + QuickbakcupmultiReforged.getModContainer().setRestoringBackup(false); + long end_time = System.currentTimeMillis(); + minecraftClient.execute(() -> { + Component title = Component.nullToEmpty(Translate.tr("quickbackupmulti.toast.end_title")); + Component desc = Component.nullToEmpty(Translate.tr("quickbackupmulti.toast.end_desc", (int) (end_time - startTime * 1000))); + SystemToast.addOrUpdate(minecraftClient.getToasts(), SystemToast.SystemToastId.PERIODIC_NOTIFICATION, title, desc); + }); + if (QuickbakcupmultiReforged.getModConfig().isClientAutoReJoinWorld()) { + minecraftClient.execute(() -> minecraftClient.createWorldOpenFlows().openWorld(levelId, + () -> minecraftClient.setScreen(null))); + } else { + minecraftClient.execute(() -> minecraftClient.setScreen(null)); + } + }, Executors.newSingleThreadExecutor()); + } + + private void handleCancellation() { + try { + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.delete_origin_save")); + deleteWorld(); + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.restore_temp_backup")); + BackupManager.restoreBackup("restore_temp"); + minecraftClient.execute(() -> { + Component title = Component.nullToEmpty(Translate.tr("quickbackupmulti.toast.cancel_success")); + Component desc = Component.nullToEmpty(Translate.tr("quickbackupmulti.toast.cancel_success.desc")); + SystemToast.addOrUpdate(minecraftClient.getToasts(), SystemToast.SystemToastId.PERIODIC_NOTIFICATION, title, desc); + minecraftClient.setScreen(null); + }); + } catch (Exception e) { + QuickbakcupmultiReforged.logger.error("Error during cancellation", e); + } + } + + public void cancel(Button button) { + isCancelled.set(true); + + if (button != null) { + screen.setState(Translate.tr("quickbackupmulti.restoring_backup.state.cancel")); + button.active = false; + } + + QuickbakcupmultiReforged.getModContainer().setRestoringBackup(false); + } + + private void deleteWorld() { + try(LevelStorageSource.LevelStorageAccess levelStorageSource = minecraftClient.getLevelSource().createAccess(levelId)) { + levelStorageSource.deleteLevel(); + } catch (IOException e) { + QuickbakcupmultiReforged.logger.error("Error during delete level", e); + } + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/RestoreTimer.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/RestoreTimer.java new file mode 100644 index 0000000..1298052 --- /dev/null +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/restore/RestoreTimer.java @@ -0,0 +1,34 @@ +package io.github.skydynamic.quickbakcupmulti.restore; + +import io.github.skydynamic.quickbakcupmulti.ModEnvType; +import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; +import io.github.skydynamic.quickbakcupmulti.command.RestoreCommand; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; +import java.util.TimerTask; + +public class RestoreTimer extends TimerTask { + private final ModEnvType env; + private final List players; + + public RestoreTimer(ModEnvType env, List players) { + this.env = env; + this.players = players; + } + + @Override + public void run() { + RestoreCommand.getRestoreDataMap().clear(); + QuickbakcupmultiReforged.getModContainer().setRestoringBackup(true); + if (env == ModEnvType.SERVER) { + for (ServerPlayer player : players) { + player.connection.disconnect(Component.literal("Server restore backup")); + } + QuickbakcupmultiReforged.getServerManager().stopServer(); + } else { + new ClientRestoreDelegate().run(); + } + } +} diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/CronUtils.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/CronUtils.java index 0a215b7..f3253a3 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/CronUtils.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/CronUtils.java @@ -19,18 +19,16 @@ public enum ScheduleMode { } } - public static Trigger buildTrigger(String name, ScheduleMode mode, T value, String jitter) { + public static Trigger buildTrigger(String name, ScheduleMode mode, T value) { IllegalArgumentException buildException = new IllegalArgumentException("Schedule mode %s requires value of type %s, but got %s (value: %s)" .formatted(mode.name(), mode.type.getName(), value.getClass().getName(), value)); - int jitterSeconds = DurationUtils.parseAndRandom(jitter); switch (mode) { case INTERVAL -> { if (value instanceof Integer v) { return TriggerBuilder.newTrigger() .withIdentity(name) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(v).repeatForever()) - .startAt(new Date(System.currentTimeMillis() + v * 1000L + jitterSeconds * 1000L)) - .usingJobData("jitter", jitterSeconds) + .startAt(new Date(System.currentTimeMillis() + v * 1000L)) .build(); } else { QuickbakcupmultiReforged.logger.error("Failed to build schedule trigger", buildException); @@ -45,8 +43,7 @@ public static Trigger buildTrigger(String name, ScheduleMode mode, T value, return TriggerBuilder.newTrigger() .withIdentity(name) .withSchedule(CronScheduleBuilder.cronSchedule(v)) - .startAt(new Date(getNextExecutionTime(v).getTime() + jitterSeconds * 1000L)) - .usingJobData("jitter", jitterSeconds) + .startAt(new Date(getNextExecutionTime(v).getTime())) .build(); } else { QuickbakcupmultiReforged.logger.error("Failed to build schedule trigger", buildException); diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/ScheduleManager.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/ScheduleManager.java index f4af7a2..1f5eebe 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/ScheduleManager.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/ScheduleManager.java @@ -9,13 +9,13 @@ private static void registerSchedule(ModSchedule schedule) { QuickbakcupmultiReforged.logger.info("Register schedule: {}", schedule.getName()); } - public static void registerSchedule(String name, String crontab, String jitter, Runnable executor) { - ModSchedule schedule = new ModSchedule(name, crontab, jitter).setExcutor(executor); + public static void registerSchedule(String name, String crontab, Runnable executor) { + ModSchedule schedule = new ModSchedule(name, crontab).setExcutor(executor); registerSchedule(schedule); } - public static void registerSchedule(String name, int interval, String jitter, Runnable executor) { - ModSchedule schedule = new ModSchedule(name, interval, jitter).setExcutor(executor); + public static void registerSchedule(String name, int interval, Runnable executor) { + ModSchedule schedule = new ModSchedule(name, interval).setExcutor(executor); registerSchedule(schedule); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/impl/ModSchedule.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/impl/ModSchedule.java index b23c918..0f50e7a 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/impl/ModSchedule.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/impl/ModSchedule.java @@ -14,7 +14,6 @@ public class ModSchedule implements IModSchedule, Job { private String crontab; private Integer interval; - private String jitter; private Runnable executor; @@ -27,16 +26,14 @@ public class ModSchedule implements IModSchedule, Job { public ModSchedule() { } - public ModSchedule(String identity, Integer interval, String jitter) { + public ModSchedule(String identity, Integer interval) { this.identity = identity; this.interval = interval; - this.jitter = jitter; } - public ModSchedule(String identity, String crontab, String jitter) { + public ModSchedule(String identity, String crontab) { this.identity = identity; this.crontab = crontab; - this.jitter = jitter; } @Override @@ -50,9 +47,9 @@ public boolean startSchedule() { StdSchedulerFactory sf = new StdSchedulerFactory(); if (crontab != null && !crontab.isEmpty() && interval == null) { - trigger = buildTrigger(identity, CronUtils.ScheduleMode.CRONTAB, crontab, jitter); + trigger = buildTrigger(identity, CronUtils.ScheduleMode.CRONTAB, crontab); } else if (interval != null && interval > 0 && crontab == null) { - trigger = buildTrigger(identity, CronUtils.ScheduleMode.INTERVAL, interval, jitter); + trigger = buildTrigger(identity, CronUtils.ScheduleMode.INTERVAL, interval); } else { return false; } @@ -99,9 +96,7 @@ public boolean isRunning() { @Override public long getNextExecuteTime() { - JobDataMap dataMap = trigger.getJobDataMap(); - int jitter = dataMap.getInt("jitter"); - return trigger.getNextFireTime().getTime() + jitter * 1000L; + return trigger.getNextFireTime().getTime(); } @Override @@ -115,21 +110,12 @@ public boolean resetTimer() { @Override public void execute(JobExecutionContext jobExecutionContext) { - JobDataMap dataMap = jobExecutionContext.getMergedJobDataMap(); - int jitter = dataMap.getInt("jitter"); - - try { - Thread.sleep(jitter * 1000L); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - QuickbakcupmultiReforged.logger.info("Schedule {} execute in {}", identity, QuickbakcupmultiReforged.formatTimestamp(System.currentTimeMillis())); executor.run(); QuickbakcupmultiReforged.logger.info( "Schedule {} execute done, next execute time: {}", identity, - QuickbakcupmultiReforged.formatTimestamp(jobExecutionContext.getTrigger().getNextFireTime().getTime() + jitter * 1000L) + QuickbakcupmultiReforged.formatTimestamp(jobExecutionContext.getTrigger().getNextFireTime().getTime()) ); } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultDatabaseBackupRunnable.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultDatabaseBackupRunnable.java index a8b8156..3232a32 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultDatabaseBackupRunnable.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultDatabaseBackupRunnable.java @@ -12,7 +12,7 @@ public class DefaultDatabaseBackupRunnable implements Runnable { public void run() { Path storagePath = Path.of(QuickbakcupmultiReforged.getModConfig().getStoragePath()); File databaseFile = storagePath.resolve("QuickBakcupMulti.mv.db").toFile(); - File databaseBackupPath = storagePath.resolve("databaseBackup").toFile(); + File databaseBackupPath = storagePath.resolve(QuickbakcupmultiReforged.getModContainer().getLevelId()).resolve("databaseBackup").toFile(); if (!databaseBackupPath.exists()) { databaseBackupPath.mkdirs(); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultPruneRunnable.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultPruneRunnable.java index 11f7d17..479a10a 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultPruneRunnable.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/schedule/runnables/DefaultPruneRunnable.java @@ -12,19 +12,46 @@ import java.util.*; public class DefaultPruneRunnable implements Runnable { + private final PruneScheduleConfig config; + private final PruneRunnable executor; + + public final static DefaultPruneRunnable PRUNE_REGULAR_BACKUP_RUNNABLE = new DefaultPruneRunnable( + QuickbakcupmultiReforged.getModConfig().getPruneScheduleConfig(), + DefaultPruneRunnable::defaultPruneRegularBackup + ); + + public final static DefaultPruneRunnable PRUNE_TEMPORARY_BACKUP_RUNNABLE = new DefaultPruneRunnable( + QuickbakcupmultiReforged.getModConfig().getPruneScheduleConfig(), + DefaultPruneRunnable::defaultPruneTemporaryBackup + ); + + public DefaultPruneRunnable(PruneScheduleConfig config, PruneRunnable executor) { + this.config = config; + this.executor = executor; + } + @Override public void run() { - PruneScheduleConfig pruneScheduleConfig = QuickbakcupmultiReforged.getModConfig().getPruneScheduleConfig(); CommandSourceStack commandSourceStack = QuickbakcupmultiReforged.getServerManager().getCommandSource(); + executor.execute(config, commandSourceStack); + } + + private static void defaultPruneRegularBackup(PruneScheduleConfig config, CommandSourceStack commandSourceStack) { List backupList = QuickbakcupmultiReforged.getDatabase().getAllStorageInfo(); - List toDelete = filterBackupWithPbs(pruneScheduleConfig.getRegularBackup(), backupList, pruneScheduleConfig.getTimezoneOverride()); + List toDelete = filterBackupWithPbs(config.getRegularBackup(), backupList, config.getTimezoneOverride()); toDelete.forEach(backup -> BackupManager.deleteBackup(commandSourceStack, backup.getName())); QuickbakcupmultiReforged.logger.info("Prune backup: {}", toDelete.size()); } - private List filterBackupWithPbs(PbsConfig pbsConfig, List backupList, String timezoneOverride) { + private static void defaultPruneTemporaryBackup(PruneScheduleConfig config, CommandSourceStack commandSourceStack) { + QuickbakcupmultiReforged.getManager().deleteTempStorage(); + + QuickbakcupmultiReforged.logger.info("Prune temporary backup success"); + } + + private static List filterBackupWithPbs(PbsConfig pbsConfig, List backupList, String timezoneOverride) { if (pbsConfig == null || !pbsConfig.isEnabled() || backupList == null || backupList.isEmpty()) { return new ArrayList<>(); } @@ -88,4 +115,9 @@ private List filterBackupWithPbs(PbsConfig pbsConfig, List hashMap = QuickbakcupmultiReforged.getDatabase().getFileHashMap(name); Path savePath = QuickbakcupmultiReforged.getModContainer().getCurrentSavePath(); try { + int index = 0; for (Map.Entry entry : hashMap.entrySet()) { String fileHash = entry.getKey(); String fileName = entry.getValue(); - String hashStart = fileHash.substring(0, 2); - File hashFile = new File(QuickbakcupmultiReforged.getModConfig().getStoragePath(), "blogs/" + hashStart + "/" + fileHash); + File hashFile; + if (fileHash.startsWith("blog_temp")) { + hashFile = getBackupPath().resolve("blogs_temp").resolve(fileHash).toFile(); + } else { + String hashStart = fileHash.substring(0, 2); + hashFile = getBackupPath().resolve("blogs").resolve(hashStart).resolve(fileHash).toFile(); + } File targetDir = savePath.resolve(fileName).toFile(); FileUtils.copyFile(hashFile, targetDir); + + index++; + + if (extraRunnable != null) { + extraRunnable.execute(hashMap.size(), index); + } } } catch (IOException e) { logger.error("Restore Failed", e); } } + + public static void restoreBackup(String name) { + restoreBackup(name, null); + } + + public static void deleteWorld(String worldName) { + try { + FileUtils.deleteDirectory(getBackupPath().toFile()); + DatabaseManager databaseManager = new DatabaseManager( + "QuickBakcupMulti", + QuickbakcupmultiReforged.getModConfig().getStoragePath(), + UUID.nameUUIDFromBytes(worldName.getBytes()) + ); + Database database = new Database(databaseManager); + List storageInfoList = database.getAllStorageInfo(); + for (StorageInfo storageInfo : storageInfoList) { + database.deleteTableValue(storageInfo.getName(), DatabaseTables.FILE_HASH); + database.deleteTableValue(storageInfo.getName(), DatabaseTables.STORAGE_INFO); + } + } catch (IOException e) { + QuickbakcupmultiReforged.logger.error("Delete Failed", e); + } + } + + @FunctionalInterface + public interface RestoreExtraRunnable { + void execute(int totalProgress, int currentProgress); + } } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/ListBackupsUtils.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/ListBackupsUtils.java index 5f721ba..b2dfdfe 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/ListBackupsUtils.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/ListBackupsUtils.java @@ -23,7 +23,6 @@ public class ListBackupsUtils { private static final Logger logger = LoggerFactory.getLogger("Qbm-ListBackupsUtil"); - private static final Path backupPath = BackupManager.getBackupPath(); private static final int BACKUPS_PER_PAGE = 5; private static long getDirSize(File dir) { @@ -112,7 +111,7 @@ public static MutableComponent list(int page) { return Component.literal(tr("quickbackupmulti.list_empty")); } int totalPage = getTotalPage(backupsInfoList); - File blogsDir = backupPath.resolve("blogs").toFile(); + File blogsDir = BackupManager.getBackupPath().resolve("blogs").toFile(); if (blogsDir.exists()) { totalBackupSizeB = getDirSize(blogsDir); } diff --git a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/UpdateChecker.java b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/UpdateChecker.java index 7fbf943..418d849 100644 --- a/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/UpdateChecker.java +++ b/common/src/main/java/io/github/skydynamic/quickbakcupmulti/utils/UpdateChecker.java @@ -41,7 +41,6 @@ public void run() { if (release != null) { ModVersion releaseVersion = new ModVersion(release.get("tag_name").getAsString()); - System.out.println(releaseVersion); if (releaseVersion.isNewerThan(currentVersion)) { QuickbakcupmultiReforged.logger.info("New version available: {}", releaseVersion); QuickbakcupmultiReforged.logger.info("Download link: {}", release.get("html_url").getAsString()); diff --git a/common/src/main/resources/assets/quickbackupmulti/lang/en_us.json b/common/src/main/resources/assets/quickbackupmulti/lang/en_us.json index 8b1f8f6..d5eb9bf 100644 --- a/common/src/main/resources/assets/quickbackupmulti/lang/en_us.json +++ b/common/src/main/resources/assets/quickbackupmulti/lang/en_us.json @@ -38,5 +38,18 @@ "quickbackupmulti.show.fail": "Backup Not Found", "quickbackupmulti.permission.set": "Set §g%s§r permission to §3%s", "quickbackupmulti.permission.reload": "Reload permission config success", - "quickbackupmulti.restartmode.switch": "§aRestart mode§r switch to: §4§l%s§r" + "quickbackupmulti.restartmode.switch": "§aRestart mode§r switch to: §4§l%s§r", + "quickbackupmulti.rejoin.switch": "§aRejoin mode§r switch to: §4§l%s§r", + "quickbackupmulti.toast.end_title": "Restore Finish!", + "quickbackupmulti.toast.end_desc": "Total time: %ss", + "quickbackupmulti.toast.cancel_success": "Restore task cancelled successfully", + "quickbackupmulti.toast.cancel_success.desc": "Your save has been restored to before the restore", + "quickbackupmulti.restoring_backup.state.make_temp_backup": "Creating temporary backup...", + "quickbackupmulti.restoring_backup.state.delete_origin_save": "Deleting original save...", + "quickbackupmulti.restoring_backup.state.restoring_backup": "Restoring backup...", + "quickbackupmulti.restoring_backup.state.restore_temp_backup": "Restoring save from before backup execution...", + "quickbackupmulti.restoring_backup.state.cancel": "Cancelling restore backup...", + "quickbackupmulti.screen.restore_screen.title": "Restore Visualization Screen", + "quickbackupmulti.screen.restore_screen.progress": "Progress: ", + "quickbackupmulti.screen.restore_screen.cancel_button": "Cancel" } diff --git a/common/src/main/resources/assets/quickbackupmulti/lang/zh_cn.json b/common/src/main/resources/assets/quickbackupmulti/lang/zh_cn.json index 99109bb..a9d9091 100644 --- a/common/src/main/resources/assets/quickbackupmulti/lang/zh_cn.json +++ b/common/src/main/resources/assets/quickbackupmulti/lang/zh_cn.json @@ -38,5 +38,18 @@ "quickbackupmulti.show.fail": "槽位不存在", "quickbackupmulti.permission.set": "设置§g%s§r的权限为§3%s", "quickbackupmulti.permission.reload": "重载权限配置文件完成", - "quickbackupmulti.restartmode.switch": "重启方式切换为: §a%s" + "quickbackupmulti.restartmode.switch": "重启方式切换为: §a%s", + "quickbackupmulti.rejoin.switch": "§a存档自动重新加入§r切换为: §4§l%s§r", + "quickbackupmulti.toast.end_title": "回档完成", + "quickbackupmulti.toast.end_desc": "总耗时: %ss", + "quickbackupmulti.toast.cancel_success": "成功取消回档任务", + "quickbackupmulti.toast.cancel_success.desc": "您的存档已恢复到回档前", + "quickbackupmulti.restoring_backup.state.make_temp_backup": "正在生成临时备份...", + "quickbackupmulti.restoring_backup.state.delete_origin_save": "正在删除原存档...", + "quickbackupmulti.restoring_backup.state.restoring_backup": "正在回档...", + "quickbackupmulti.restoring_backup.state.restore_temp_backup": "正在还原执行备份前的存档...", + "quickbackupmulti.restoring_backup.state.cancel": "正在取消还原备份...", + "quickbackupmulti.screen.restore_screen.title": "回档可视化页面", + "quickbackupmulti.screen.restore_screen.progress": "进度: ", + "quickbackupmulti.screen.restore_screen.cancel_button": "取消" } diff --git a/common/src/main/resources/quickbakcupmulti.mixins.json b/common/src/main/resources/quickbakcupmulti.mixins.json index 86cfe04..4df00d6 100644 --- a/common/src/main/resources/quickbakcupmulti.mixins.json +++ b/common/src/main/resources/quickbakcupmulti.mixins.json @@ -4,12 +4,16 @@ "compatibilityLevel": "JAVA_21", "minVersion": "0.8", "client": [ - "client.MixinClientPacketListener" + "client.MixinIntegratedServer", + "client.MixinWorldOpenFlows", + "client.MixinWorldSelectionListWorldListEntry" ], - "mixins": [ - "MixinMinecraftServer" + "server": [ + "MixinDedicatedServer" ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + ] } diff --git a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/QuickbakcupmultiReforgedFabric.java b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/QuickbakcupmultiReforgedFabric.java index 31fbbdc..dae65ef 100644 --- a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/QuickbakcupmultiReforgedFabric.java +++ b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/QuickbakcupmultiReforgedFabric.java @@ -1,21 +1,26 @@ package io.github.skydynamic.quickbakcupmulti.fabric; +import io.github.skydynamic.quickbakcupmulti.ModEnvType; import io.github.skydynamic.quickbakcupmulti.ModVersion; import io.github.skydynamic.quickbakcupmulti.fabric.events.FabricEvents; import io.github.skydynamic.quickbakcupmulti.ModContainer; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; import lombok.Getter; import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; -public final class QuickbakcupmultiReforgedFabric implements DedicatedServerModInitializer { +public final class QuickbakcupmultiReforgedFabric implements ModInitializer { @Getter private static final ModContainer modContainer = new ModContainer(); @Override - public void onInitializeServer() { + public void onInitialize() { modContainer.setConfigPath(FabricLoader.getInstance().getConfigDir()); + modContainer.setEnvType(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ? ModEnvType.CLIENT : ModEnvType.SERVER); + FabricLoader.getInstance().getModContainer(QuickbakcupmultiReforged.MOD_ID).ifPresent(modContainer1 -> modContainer.setModVersion(new ModVersion(modContainer1.getMetadata().getVersion().getFriendlyString()))); diff --git a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/client/QuickbakcupmultiReforgedFabricClient.java b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/client/QuickbakcupmultiReforgedFabricClient.java index 0e5d8ff..f9ab057 100644 --- a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/client/QuickbakcupmultiReforgedFabricClient.java +++ b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/client/QuickbakcupmultiReforgedFabricClient.java @@ -1,11 +1,10 @@ package io.github.skydynamic.quickbakcupmulti.fabric.client; -import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; import net.fabricmc.api.ClientModInitializer; public final class QuickbakcupmultiReforgedFabricClient implements ClientModInitializer { @Override public void onInitializeClient() { - QuickbakcupmultiReforged.logger.warn("QuickbackupmultiReforged mod is not supporting clients now!!!"); + } } diff --git a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/events/FabricEvents.java b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/events/FabricEvents.java index 3acbf2d..5eb7d98 100644 --- a/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/events/FabricEvents.java +++ b/fabric/src/main/java/io/github/skydynamic/quickbakcupmulti/fabric/events/FabricEvents.java @@ -17,14 +17,14 @@ public static void register() { QuickbakcupmultiReforged.registerCommand(); } ); - ServerLifecycleEvents.SERVER_STOPPED.register(FabricEvents::onServerStoped); + ServerLifecycleEvents.SERVER_STOPPED.register(FabricEvents::onServerStopped); } private static void onServerStarted(MinecraftServer server) { QuickbakcupmultiReforged.setServerManager(new ServerManager(server)); } - private static void onServerStoped(MinecraftServer server) { + private static void onServerStopped(MinecraftServer server) { OnServerStoppedHandler.handle(); } } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index c75621e..7ae171c 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -15,7 +15,7 @@ "accessWidener": "qbm.accesswidener", "environment": "*", "entrypoints": { - "server": [ + "main": [ "io.github.skydynamic.quickbakcupmulti.fabric.QuickbakcupmultiReforgedFabric" ], "client": [ diff --git a/gradle.properties b/gradle.properties index 9658ba3..e6ec036 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,17 +2,17 @@ org.gradle.jvmargs = -Xmx2G org.gradle.parallel = true # Mod properties -mod_version = 3.1.1 +mod_version = 3.2.0 maven_group = io.github.skydynamic mod_id = quickbakcupmulti_reforged archives_name = QuickBakcupMulti-Reforged enabled_platforms = fabric,neoforge # Minecraft properties minecraft_version = 1.21 -minecraft_supported_versions = >=1.21 <1.21.5 +minecraft_supported_versions = [1.21, 1.21.5) # Dependencies fabric_loader_version = 0.16.10 fabric_api_version = 0.102.0+1.21 neoforge_version = 21.0.167 -incremental_storage_lib_version=1.2.0+build.25062418.20 +incremental_storage_lib_version=1.2.0+build.25073015.53 diff --git a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/QuickbakcupmultiReforgedNeoForge.java b/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/QuickbakcupmultiReforgedNeoForge.java index 0199f6e..9e30503 100644 --- a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/QuickbakcupmultiReforgedNeoForge.java +++ b/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/QuickbakcupmultiReforgedNeoForge.java @@ -1,6 +1,7 @@ package io.github.skydynamic.quickbakcupmulti.neoforge; import io.github.skydynamic.quickbakcupmulti.ModContainer; +import io.github.skydynamic.quickbakcupmulti.ModEnvType; import io.github.skydynamic.quickbakcupmulti.ModVersion; import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; import lombok.Getter; @@ -10,7 +11,7 @@ import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.FMLPaths; -@Mod(value = QuickbakcupmultiReforged.MOD_ID, dist = Dist.DEDICATED_SERVER) +@Mod(value = QuickbakcupmultiReforged.MOD_ID) public final class QuickbakcupmultiReforgedNeoForge { @Getter private static final ModContainer modContainer = new ModContainer(); @@ -19,6 +20,7 @@ public final class QuickbakcupmultiReforgedNeoForge { public QuickbakcupmultiReforgedNeoForge() { modContainer.setConfigPath(FMLPaths.CONFIGDIR.get()); + modContainer.setEnvType(FMLLoader.getDist().isClient() ? ModEnvType.CLIENT : ModEnvType.SERVER); String version = FMLLoader.getLoadingModList().getModFileById(QuickbakcupmultiReforged.MOD_ID).getMods().getFirst().getVersion().toString(); modContainer.setModVersion(new ModVersion(version)); diff --git a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/client/QuickbackupmultiReforgedNeoForgeClient.java b/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/client/QuickbackupmultiReforgedNeoForgeClient.java deleted file mode 100644 index 69b36c3..0000000 --- a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/client/QuickbackupmultiReforgedNeoForgeClient.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.skydynamic.quickbakcupmulti.neoforge.client; - -import io.github.skydynamic.quickbakcupmulti.QuickbakcupmultiReforged; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.fml.common.Mod; - -@Mod(value = QuickbakcupmultiReforged.MOD_ID, dist = Dist.CLIENT) -public class QuickbackupmultiReforgedNeoForgeClient { - public QuickbackupmultiReforgedNeoForgeClient() { - QuickbakcupmultiReforged.logger.warn("QuickbackupmultiReforged mod is not supporting clients now!!!"); - } -} diff --git a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/events/NeoForgeEvents.java b/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/events/NeoForgeEvents.java index 5c36466..b78fc7d 100644 --- a/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/events/NeoForgeEvents.java +++ b/neoforge/src/main/java/io/github/skydynamic/quickbakcupmulti/neoforge/events/NeoForgeEvents.java @@ -7,13 +7,14 @@ import io.github.skydynamic.quickbakcupmulti.neoforge.ServerManagerNeoforge; import io.github.skydynamic.quickbakcupmulti.utils.BackupManager; import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.RegisterCommandsEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppedEvent; -@EventBusSubscriber(value = Dist.DEDICATED_SERVER, modid = QuickbakcupmultiReforged.MOD_ID) +@EventBusSubscriber(modid = QuickbakcupmultiReforged.MOD_ID) public class NeoForgeEvents { @SubscribeEvent public static void onRegisterCommands(RegisterCommandsEvent event) { @@ -31,8 +32,9 @@ public static void onServerStarted(ServerStartedEvent event) { } } + @OnlyIn(Dist.DEDICATED_SERVER) @SubscribeEvent - public static void onServerStopped(ServerStoppedEvent event) { + public static void onDedicatedServerStopped(ServerStoppedEvent event) { if (QuickbakcupmultiReforged.getModContainer().isRestoringBackup()) { if (QuickbakcupmultiReforged.getModConfig().getAutoRestartMode() == ModConfig.AutoRestartMode.DEFAULT) { BackupManager.restoreBackup(QuickbakcupmultiReforged.getModContainer().getCurrentSelectionBackup()); @@ -42,4 +44,10 @@ public static void onServerStopped(ServerStoppedEvent event) { } } } + + @OnlyIn(Dist.CLIENT) + @SubscribeEvent + public static void onIntegratedServerStopped(ServerStoppedEvent event) { + OnServerStoppedHandler.handle(); + } } diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 30cc7d8..bd33b9e 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -23,7 +23,7 @@ side = "BOTH" [[dependencies.quickbakcupmulti_reforged]] modId = "minecraft" type = "required" -versionRange = "[1.21,)" +versionRange = "[1.21, 1.21.5)" ordering = "NONE" side = "BOTH"