From e264293f61933dd3e8444527df3c9b5feb47e3a5 Mon Sep 17 00:00:00 2001 From: Mehrad Nayyeri Date: Sat, 22 Apr 2023 16:02:33 +0330 Subject: [PATCH] Changed the BackupManager system to be more expandable and sustainable --- .../rollback/event/AutomatedBackup.java | 60 ++--- .../rollback/event/RollbackCommand.java | 17 +- .../mehradn/rollback/gui/RollbackScreen.java | 15 +- .../rollback/gui/RollbackSelectionList.java | 16 +- .../mixin/CreateWorldScreenMixin.java | 5 +- .../rollback/mixin/MinecraftServerMixin.java | 2 +- .../rollback/mixin/WorldListEntryMixin.java | 11 +- .../rollback/util/backup/BackupManager.java | 225 ++++++------------ .../rollback/util/backup/MetadataUpdater.java | 38 ++- .../rollback/util/backup/RollbackBackup.java | 50 +--- .../rollback/util/backup/RollbackWorld.java | 29 +++ .../util/gson/LocalDateTimeAdapter.java | 32 +++ .../gson/MetadataUpdaterVersionAdapter.java | 21 ++ .../rollback/util/gson/PathAdapter.java | 21 ++ .../resources/assets/rollback/lang/en_us.json | 1 + 15 files changed, 281 insertions(+), 262 deletions(-) create mode 100644 src/main/java/ir/mehradn/rollback/util/backup/RollbackWorld.java create mode 100644 src/main/java/ir/mehradn/rollback/util/gson/LocalDateTimeAdapter.java create mode 100644 src/main/java/ir/mehradn/rollback/util/gson/MetadataUpdaterVersionAdapter.java create mode 100644 src/main/java/ir/mehradn/rollback/util/gson/PathAdapter.java diff --git a/src/main/java/ir/mehradn/rollback/event/AutomatedBackup.java b/src/main/java/ir/mehradn/rollback/event/AutomatedBackup.java index 793f81c..d6bb7c1 100644 --- a/src/main/java/ir/mehradn/rollback/event/AutomatedBackup.java +++ b/src/main/java/ir/mehradn/rollback/event/AutomatedBackup.java @@ -3,6 +3,7 @@ import ir.mehradn.rollback.Rollback; import ir.mehradn.rollback.config.RollbackConfig; import ir.mehradn.rollback.util.backup.BackupManager; +import ir.mehradn.rollback.util.backup.RollbackWorld; import ir.mehradn.rollback.util.mixin.MinecraftServerExpanded; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -11,14 +12,12 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; -import org.apache.commons.lang3.tuple.Triple; @Environment(EnvType.CLIENT) public final class AutomatedBackup { + private static BackupManager backupManager; + private static RollbackWorld rollbackWorld; private static int latestUpdate; - private static int daysPassed; - private static int sinceDay; - private static int sinceBackup; public static void register() { ServerLifecycleEvents.SERVER_STARTED.register(AutomatedBackup::onServerStarted); @@ -28,14 +27,10 @@ public static void register() { public static void onServerStarted(MinecraftServer server) { Rollback.LOGGER.info("Reading the timer information..."); - BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); String worldName = ((MinecraftServerExpanded)server).getLevelAccess().getLevelId(); - Triple info = backupManager.getTimerInformation(worldName); - + backupManager = ((MinecraftServerExpanded)server).getBackupManager(); + rollbackWorld = backupManager.getWorld(worldName); latestUpdate = server.getTickCount(); - daysPassed = info.getLeft(); - sinceDay = info.getMiddle(); - sinceBackup = info.getRight(); } public static void onEndTick(MinecraftServer server) { @@ -45,40 +40,31 @@ public static void onEndTick(MinecraftServer server) { if (shouldUpdate(serverTick, worldTick)) { Rollback.LOGGER.info("Updating the timer information..."); - BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); - String worldName = ((MinecraftServerExpanded)server).getLevelAccess().getLevelId(); - int timePassed = serverTick - latestUpdate; latestUpdate = serverTick; - sinceDay += timePassed; - sinceBackup += timePassed; - if (isMorning(worldTick) && sinceDay >= 11900) { - daysPassed++; - sinceDay = 0; + rollbackWorld.ticksSinceLastMorning += timePassed; + rollbackWorld.ticksSinceLastBackup += timePassed; + if (isMorning(worldTick) && rollbackWorld.ticksSinceLastMorning >= 11900) { + rollbackWorld.daysSinceLastBackup++; + rollbackWorld.ticksSinceLastMorning = 0; } - if (shouldCreateBackup(worldTick, backupManager, worldName)) { + if (shouldCreateBackup(worldTick)) { Rollback.LOGGER.info("Creating an automated backup..."); - backupManager.createRollbackBackup(server, true); - daysPassed = 0; - sinceDay = 0; - sinceBackup = 0; - } else { - backupManager.setTimerInformation(worldName, daysPassed, sinceBackup); + backupManager.createRollbackBackup(server); + rollbackWorld.resetTimers(); } + + backupManager.saveMetadata(); } } public static void onServerStopping(MinecraftServer server) { int serverTick = server.getTickCount(); - BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); - String worldName = ((MinecraftServerExpanded)server).getLevelAccess().getLevelId(); - int timePassed = serverTick - latestUpdate; - sinceDay += timePassed; - sinceBackup += timePassed; - - backupManager.setTimerInformation(worldName, daysPassed, sinceDay, sinceBackup); + rollbackWorld.ticksSinceLastMorning += timePassed; + rollbackWorld.ticksSinceLastBackup += timePassed; + backupManager.saveMetadata(); } private static boolean isMorning(int worldTick) { @@ -88,14 +74,14 @@ private static boolean isMorning(int worldTick) { } private static boolean shouldUpdate(int serverTick, int worldTick) { - return (isMorning(worldTick) || (serverTick - latestUpdate + sinceBackup) % 24000 == 0); + return (isMorning(worldTick) || (serverTick - latestUpdate + rollbackWorld.ticksSinceLastBackup) % 24000 == 0); } - private static boolean shouldCreateBackup(int worldTick, BackupManager backupManager, String worldName) { - if (backupManager.getAutomated(worldName)) { + private static boolean shouldCreateBackup(int worldTick) { + if (rollbackWorld.automatedBackups) { switch (RollbackConfig.timerMode()) { - case DAYLIGHT_CYCLE -> { return (isMorning(worldTick) && daysPassed >= RollbackConfig.daysPerBackup()); } - case IN_GAME_TIME -> { return (sinceBackup >= RollbackConfig.ticksPerBackup()); } + case DAYLIGHT_CYCLE -> { return (isMorning(worldTick) && rollbackWorld.daysSinceLastBackup >= RollbackConfig.daysPerBackup()); } + case IN_GAME_TIME -> { return (rollbackWorld.ticksSinceLastBackup >= RollbackConfig.ticksPerBackup()); } } } return false; diff --git a/src/main/java/ir/mehradn/rollback/event/RollbackCommand.java b/src/main/java/ir/mehradn/rollback/event/RollbackCommand.java index 3c09289..b1ee69e 100644 --- a/src/main/java/ir/mehradn/rollback/event/RollbackCommand.java +++ b/src/main/java/ir/mehradn/rollback/event/RollbackCommand.java @@ -6,6 +6,7 @@ import ir.mehradn.rollback.config.RollbackConfig; import ir.mehradn.rollback.util.backup.BackupManager; import ir.mehradn.rollback.util.backup.RollbackBackup; +import ir.mehradn.rollback.util.backup.RollbackWorld; import ir.mehradn.rollback.util.mixin.MinecraftServerExpanded; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -20,8 +21,6 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.LevelStorageSource; -import java.util.List; - @Environment(EnvType.CLIENT) public final class RollbackCommand { public static void register() { @@ -56,7 +55,7 @@ public static int createBackup(CommandContext context) { MinecraftServer server = context.getSource().getServer(); BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); - boolean f = backupManager.createRollbackBackup(server, false); + boolean f = backupManager.createRollbackBackup(server); if (!f) { context.getSource().sendFailure(Component.translatable("rollback.createBackup.failed")); return 0; @@ -98,18 +97,18 @@ public static int listBackups(CommandContext context) { return 0; MinecraftServer server = context.getSource().getServer(); - BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); String worldName = ((MinecraftServerExpanded)server).getLevelAccess().getLevelId(); - List backups = backupManager.getRollbacksFor(worldName); + BackupManager backupManager = ((MinecraftServerExpanded)server).getBackupManager(); + RollbackWorld world = backupManager.getWorld(worldName); - if (backups.isEmpty()) { + if (world.backups.isEmpty()) { context.getSource().sendSystemMessage(Component.translatable("rollback.command.list.noBackups")); return 1; } context.getSource().sendSystemMessage(Component.translatable("rollback.command.list.title")); - for (int i = 1; i <= backups.size(); i++) { - RollbackBackup backup = backups.get(backups.size() - i); + for (int i = 1; i <= world.backups.size(); i++) { + RollbackBackup backup = world.backups.get(world.backups.size() - i); MutableComponent part1, part2, part3; part1 = Component.literal(String.format(" #%-2d ", i)); part2 = Component.translatable("rollback.created", backup.getDateAsString()).append(Component.literal(" ")); @@ -123,7 +122,7 @@ private static boolean isNotServerHost(CommandContext contex LocalPlayer player1 = Minecraft.getInstance().player; ServerPlayer player2 = context.getSource().getPlayer(); if (player1 == null || player2 == null) { - context.getSource().sendFailure(Component.literal("This command can only be used by a player")); + context.getSource().sendFailure(Component.translatable("rollback.command.playerOnly")); return true; } diff --git a/src/main/java/ir/mehradn/rollback/gui/RollbackScreen.java b/src/main/java/ir/mehradn/rollback/gui/RollbackScreen.java index 6769f43..3873358 100644 --- a/src/main/java/ir/mehradn/rollback/gui/RollbackScreen.java +++ b/src/main/java/ir/mehradn/rollback/gui/RollbackScreen.java @@ -4,6 +4,7 @@ import eu.midnightdust.lib.config.MidnightConfig; import ir.mehradn.rollback.Rollback; import ir.mehradn.rollback.util.backup.BackupManager; +import ir.mehradn.rollback.util.backup.RollbackWorld; import ir.mehradn.rollback.util.mixin.WorldSelectionListCallbackAction; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -25,6 +26,7 @@ public class RollbackScreen extends Screen { private final Consumer callback; private final LevelSummary levelSummary; private final BackupManager backupManager; + private final RollbackWorld rollbackWorld; private RollbackSelectionList rollbackList; private Button rollbackButton; private Button deleteButton; @@ -33,7 +35,8 @@ public RollbackScreen(LevelSummary summary, Consumer this.backupManager.setAutomated(this.levelSummary.getLevelId(), enabled) + (button, enabled) -> { + this.rollbackWorld.automatedBackups = enabled; + this.backupManager.saveMetadata(); + } )); addRenderableWidget(Button.builder( Component.translatable("selectWorld.edit.backupFolder"), diff --git a/src/main/java/ir/mehradn/rollback/gui/RollbackSelectionList.java b/src/main/java/ir/mehradn/rollback/gui/RollbackSelectionList.java index 6d16174..efcd0f3 100644 --- a/src/main/java/ir/mehradn/rollback/gui/RollbackSelectionList.java +++ b/src/main/java/ir/mehradn/rollback/gui/RollbackSelectionList.java @@ -6,6 +6,7 @@ import ir.mehradn.rollback.Rollback; import ir.mehradn.rollback.util.backup.BackupManager; import ir.mehradn.rollback.util.backup.RollbackBackup; +import ir.mehradn.rollback.util.backup.RollbackWorld; import ir.mehradn.rollback.util.mixin.WorldSelectionListCallbackAction; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -33,15 +34,17 @@ public final class RollbackSelectionList extends ObjectSelectionList { private final RollbackScreen screen; private final BackupManager backupManager; + private final RollbackWorld rollbackWorld; private final LevelSummary summary; private final CurrentSaveEntry currentSaveEntry; private boolean shouldReloadEntries; - public RollbackSelectionList(RollbackScreen screen, BackupManager backupManager, LevelSummary levelSummary, Minecraft minecraftClient, + public RollbackSelectionList(RollbackScreen screen, BackupManager backupManager, RollbackWorld rollbackWorld, LevelSummary levelSummary, Minecraft minecraftClient, int width, int height, int top, int bottom, int itemHeight) { super(minecraftClient, width, height, top, bottom, itemHeight); this.screen = screen; this.backupManager = backupManager; + this.rollbackWorld = rollbackWorld; this.summary = levelSummary; this.currentSaveEntry = new CurrentSaveEntry(); this.shouldReloadEntries = true; @@ -77,9 +80,8 @@ private void reloadEntries() { clearEntries(); addEntry(this.currentSaveEntry); - List backups = this.backupManager.getRollbacksFor(this.summary.getLevelId()); - for (int i = 1; i <= backups.size(); i++) - addEntry(new RollbackEntry(i, backups.get(backups.size() - i))); + for (int i = 1; i <= this.rollbackWorld.backups.size(); i++) + addEntry(new RollbackEntry(i, this.rollbackWorld.backups.get(this.rollbackWorld.backups.size() - i))); this.screen.triggerImmediateNarration(true); this.shouldReloadEntries = false; @@ -198,11 +200,13 @@ private DynamicTexture getIconTexture() { @Environment(EnvType.CLIENT) public final class RollbackEntry extends Entry { private final int backupNumber; + private final String worldName; private final RollbackBackup backup; public RollbackEntry(int backupNumber, RollbackBackup rollbackBackup) { super(); this.backupNumber = backupNumber; + this.worldName = RollbackSelectionList.this.summary.getLevelId(); this.backup = rollbackBackup; this.iconLocation = new ResourceLocation("rollback", "backup/" + this.backupNumber + "/icon.png"); this.icon = getIconTexture(); @@ -231,7 +235,7 @@ public void playBackup() { (confirmed) -> { if (confirmed) { Rollback.LOGGER.info("Rolling back to backup #{}...", this.backupNumber); - boolean f = RollbackSelectionList.this.backupManager.rollbackTo(this.backup); + boolean f = RollbackSelectionList.this.backupManager.rollbackTo(this.worldName, this.backup); if (f) RollbackSelectionList.this.screen.doAction(WorldSelectionListCallbackAction.JOIN_WORLD); else @@ -251,7 +255,7 @@ public void deleteBackup() { (confirmed) -> { if (confirmed) { Rollback.LOGGER.info("Deleting the backup #{}...", this.backupNumber); - RollbackSelectionList.this.backupManager.deleteBackup(this.backup.worldName, -this.backupNumber); + RollbackSelectionList.this.backupManager.deleteBackup(this.worldName, -this.backupNumber); RollbackSelectionList.this.shouldReloadEntries = true; } this.minecraft.setScreen(RollbackSelectionList.this.screen); diff --git a/src/main/java/ir/mehradn/rollback/mixin/CreateWorldScreenMixin.java b/src/main/java/ir/mehradn/rollback/mixin/CreateWorldScreenMixin.java index 4066044..229fbef 100644 --- a/src/main/java/ir/mehradn/rollback/mixin/CreateWorldScreenMixin.java +++ b/src/main/java/ir/mehradn/rollback/mixin/CreateWorldScreenMixin.java @@ -35,8 +35,9 @@ public void setAutomatedBackups(boolean enabled) { private Optional saveOption(Optional optional) { if (optional.isPresent()) { String worldName = optional.get().getLevelId(); - BackupManager backupManager = new BackupManager(); - backupManager.setAutomated(worldName, getAutomatedBackups()); + BackupManager backupManager = BackupManager.loadMetadata(); + backupManager.getWorld(worldName).automatedBackups = getAutomatedBackups(); + backupManager.saveMetadata(); } return optional; } diff --git a/src/main/java/ir/mehradn/rollback/mixin/MinecraftServerMixin.java b/src/main/java/ir/mehradn/rollback/mixin/MinecraftServerMixin.java index 4eae0b7..df6b715 100644 --- a/src/main/java/ir/mehradn/rollback/mixin/MinecraftServerMixin.java +++ b/src/main/java/ir/mehradn/rollback/mixin/MinecraftServerMixin.java @@ -37,6 +37,6 @@ public BackupManager getBackupManager() { @Inject(method = "", at = @At("RETURN")) private void addBackupManager(CallbackInfo ci) { - this.backupManager = new BackupManager(); + this.backupManager = BackupManager.loadMetadata(); } } diff --git a/src/main/java/ir/mehradn/rollback/mixin/WorldListEntryMixin.java b/src/main/java/ir/mehradn/rollback/mixin/WorldListEntryMixin.java index 253d474..e954843 100644 --- a/src/main/java/ir/mehradn/rollback/mixin/WorldListEntryMixin.java +++ b/src/main/java/ir/mehradn/rollback/mixin/WorldListEntryMixin.java @@ -5,6 +5,7 @@ import ir.mehradn.rollback.config.RollbackConfig; import ir.mehradn.rollback.gui.RollbackScreen; import ir.mehradn.rollback.util.backup.BackupManager; +import ir.mehradn.rollback.util.backup.RollbackWorld; import ir.mehradn.rollback.util.mixin.EditWorldScreenExpanded; import ir.mehradn.rollback.util.mixin.WorldListEntryExpanded; import ir.mehradn.rollback.util.mixin.WorldSelectionListCallbackAction; @@ -72,7 +73,7 @@ public EditWorldScreen improveEditWorldScreen(EditWorldScreen screen) { @Inject(method = "deleteWorld", at = @At("RETURN")) private void deleteBackups(CallbackInfo ci) { - BackupManager backupManager = new BackupManager(); + BackupManager backupManager = BackupManager.loadMetadata(); backupManager.deleteAllBackupsFor(this.summary.getLevelId()); } @@ -82,13 +83,15 @@ private void promptFeature(CallbackInfo ci) { return; this.queueLoadScreen(); - BackupManager backupManager = new BackupManager(); String worldName = this.summary.getLevelId(); + BackupManager backupManager = BackupManager.loadMetadata(); + RollbackWorld rollbackWorld = backupManager.getWorld(worldName); - if (!backupManager.getPrompted(worldName)) { + if (!rollbackWorld.prompted) { this.minecraft.setScreen(new ConfirmScreen( (confirmed) -> { - backupManager.setPromptAnswer(worldName, confirmed); + rollbackWorld.setPromptAnswer(confirmed); + backupManager.saveMetadata(); this.loadWorld(); }, Component.translatable("rollback.screen.enableAutomatedQuestion"), diff --git a/src/main/java/ir/mehradn/rollback/util/backup/BackupManager.java b/src/main/java/ir/mehradn/rollback/util/backup/BackupManager.java index 8fa17ba..9253333 100644 --- a/src/main/java/ir/mehradn/rollback/util/backup/BackupManager.java +++ b/src/main/java/ir/mehradn/rollback/util/backup/BackupManager.java @@ -1,9 +1,16 @@ package ir.mehradn.rollback.util.backup; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; import ir.mehradn.rollback.Rollback; import ir.mehradn.rollback.config.RollbackConfig; import ir.mehradn.rollback.mixin.GameRendererAccessor; +import ir.mehradn.rollback.util.gson.LocalDateTimeAdapter; +import ir.mehradn.rollback.util.gson.MetadataUpdaterVersionAdapter; +import ir.mehradn.rollback.util.gson.PathAdapter; import ir.mehradn.rollback.util.mixin.LevelStorageAccessExpanded; import ir.mehradn.rollback.util.mixin.MinecraftServerExpanded; import net.fabricmc.api.EnvType; @@ -18,89 +25,38 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelSummary; -import org.apache.commons.lang3.tuple.Triple; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.Enumeration; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @Environment(EnvType.CLIENT) public class BackupManager { - public final Path rollbackDirectory; - public final Path iconsDirectory; - private final Path metadataFilePath; - private JsonObject metadata; - - public BackupManager() { + public transient final Path rollbackDirectory; + public transient final Path iconsDirectory; + @SerializedName("version") public MetadataUpdater.Version version = MetadataUpdater.Version.LATEST_VERSION; + @SerializedName("worlds") public Map worlds = new HashMap<>(); + private static final Gson gson = new GsonBuilder() + .registerTypeHierarchyAdapter(Path.class, PathAdapter.INSTANCE) + .registerTypeAdapter(LocalDateTime.class, LocalDateTimeAdapter.INSTANCE) + .registerTypeAdapter(MetadataUpdater.Version.class, MetadataUpdaterVersionAdapter.INSTANCE) + .create(); + + private BackupManager() { this.rollbackDirectory = Minecraft.getInstance().getLevelSource().getBackupPath().resolve("rollbacks"); this.iconsDirectory = this.rollbackDirectory.resolve("icons"); - this.metadataFilePath = this.rollbackDirectory.resolve("rollbacks.json"); - try { - loadMetadata(); - } catch (FileNotFoundException e) { - Rollback.LOGGER.warn("Metadata file not found! Creating a new one..."); - this.metadata = new JsonObject(); - saveMetadata(); - } - } - - public boolean getAutomated(String worldName) { - JsonObject worldObject = getWorldObject(worldName); - return worldObject.get("automated").getAsBoolean(); - } - - public void setAutomated(String worldName, boolean enabled) { - getWorldObject(worldName).addProperty("automated", enabled); - saveMetadata(); - } - - public boolean getPrompted(String worldName) { - JsonObject worldObject = getWorldObject(worldName); - return (worldObject.get("automated").getAsBoolean() || worldObject.get("prompted").getAsBoolean()); } - public void setPromptAnswer(String worldName, boolean automated) { - JsonObject worldObject = getWorldObject(worldName); - worldObject.addProperty("automated", automated); - worldObject.addProperty("prompted", true); - saveMetadata(); - } - - public Triple getTimerInformation(String worldName) { - JsonObject worldObject = getWorldObject(worldName); - int daysPassed = worldObject.get("days_passed").getAsInt(); - int sinceDay = worldObject.get("since_day").getAsInt(); - int sinceBackup = worldObject.get("since_backup").getAsInt(); - return Triple.of(daysPassed, sinceDay, sinceBackup); - } - - public void setTimerInformation(String worldName, int daysPassed, int sinceBackup) { - JsonObject worldObject = getWorldObject(worldName); - worldObject.addProperty("days_passed", daysPassed); - worldObject.addProperty("since_backup", sinceBackup); - saveMetadata(); - } - - public void setTimerInformation(String worldName, int daysPassed, int sinceDay, int sinceBackup) { - JsonObject worldObject = getWorldObject(worldName); - worldObject.addProperty("days_passed", daysPassed); - worldObject.addProperty("since_day", sinceDay); - worldObject.addProperty("since_backup", sinceBackup); - saveMetadata(); - } - - public List getRollbacksFor(String worldName) { - ArrayList list = new ArrayList<>(); - JsonArray array = getWorldObject(worldName).getAsJsonArray("backups"); - for (JsonElement elm : array) - list.add(new RollbackBackup(worldName, elm.getAsJsonObject())); - return list; + public RollbackWorld getWorld(String worldName) { + if (!this.worlds.containsKey(worldName)) + this.worlds.put(worldName, new RollbackWorld()); + return this.worlds.get(worldName); } public boolean createNormalBackup(LevelSummary summary) { @@ -120,17 +76,16 @@ public boolean createNormalBackup(LevelSummary summary) { } } - public boolean createRollbackBackup(MinecraftServer server, boolean automated) { + public boolean createRollbackBackup(MinecraftServer server) { Rollback.LOGGER.info("Creating a rollback backup..."); LevelStorageSource.LevelStorageAccess levelAccess = ((MinecraftServerExpanded)server).getLevelAccess(); String worldName = levelAccess.getLevelId(); LocalDateTime now = LocalDateTime.now(); - JsonObject worldObject = getWorldObject(worldName); - JsonArray array = worldObject.getAsJsonArray("backups"); + RollbackWorld world = getWorld(worldName); - while (array.size() >= RollbackConfig.maxBackupsPerWorld()) + while (world.backups.size() >= RollbackConfig.maxBackupsPerWorld()) deleteBackup(worldName, 0); - deleteNonexistentIcons(worldName); + deleteGhostIcons(worldName); Rollback.LOGGER.debug("Saving the world..."); boolean f = server.saveEverything(true, true, true); @@ -153,7 +108,7 @@ public boolean createRollbackBackup(MinecraftServer server, boolean automated) { try { path2 = this.rollbackDirectory.resolve(FileUtil.findAvailableName( this.rollbackDirectory, - now.format(RollbackBackup.TIME_FORMATTER) + "_" + worldName, + now.format(LocalDateTimeAdapter.TIME_FORMATTER) + "_" + worldName, ".zip" )); Files.move(path1, path2); @@ -183,28 +138,22 @@ public boolean createRollbackBackup(MinecraftServer server, boolean automated) { Rollback.LOGGER.debug("Adding the metadata..."); path2 = this.rollbackDirectory.relativize(path2); path3 = this.rollbackDirectory.relativize(path3); - RollbackBackup backup = new RollbackBackup(worldName, path2, path3, LocalDateTime.now(), daysPlayed); - array.add(backup.toObject()); - if (automated) { - worldObject.addProperty("days_passed", 0); - worldObject.addProperty("since_day", 0); - worldObject.addProperty("since_backup", 0); - } - saveMetadata(); + RollbackBackup backup = new RollbackBackup(path2, path3, LocalDateTime.now(), daysPlayed); + world.backups.add(backup); + saveMetadata(); return true; } public boolean deleteBackup(String worldName, int index) { Rollback.LOGGER.info("Deleting the backup #{}...", index); - JsonArray array = getWorldObject(worldName).getAsJsonArray("backups"); + RollbackWorld world = getWorld(worldName); - if (array.size() <= index || index < -array.size()) + if (world.backups.size() <= index || index < -world.backups.size()) return true; if (index < 0) - index += array.size(); - - RollbackBackup backup = new RollbackBackup(worldName, array.get(index).getAsJsonObject()); + index += world.backups.size(); + RollbackBackup backup = world.backups.get(index); Rollback.LOGGER.debug("Deleting the files..."); try { @@ -216,18 +165,17 @@ public boolean deleteBackup(String worldName, int index) { return false; } - array.remove(index); + world.backups.remove(index); saveMetadata(); - return true; } - public boolean rollbackTo(RollbackBackup backup) { + public boolean rollbackTo(String worldName, RollbackBackup backup) { Rollback.LOGGER.info("Rolling back to backup \"{}\"...", backup.backupPath.toString()); Minecraft client = Minecraft.getInstance(); Rollback.LOGGER.debug("Deleting the current save..."); - try (LevelStorageSource.LevelStorageAccess levelAccess = client.getLevelSource().createAccess(backup.worldName)) { + try (LevelStorageSource.LevelStorageAccess levelAccess = client.getLevelSource().createAccess(worldName)) { levelAccess.deleteLevel(); } catch (IOException e) { showError("rollback.rollback.failed", "Failed to delete the current save!", e); @@ -262,59 +210,22 @@ public boolean rollbackTo(RollbackBackup backup) { public void deleteAllBackupsFor(String worldName) { Rollback.LOGGER.info("Deleting all the backups for world \"{}\"...", worldName); - - JsonArray array = getWorldObject(worldName).getAsJsonArray("backups"); - while (array.size() > 0) + RollbackWorld world = getWorld(worldName); + while (world.backups.size() > 0) deleteBackup(worldName, 0); - this.metadata.getAsJsonObject("worlds").remove(worldName); - + this.worlds.remove(worldName); saveMetadata(); } - private JsonObject getWorldObject(String worldName) { - if (!this.metadata.has("worlds")) - this.metadata.add("worlds", new JsonObject()); - JsonObject worldsData = this.metadata.getAsJsonObject("worlds"); - - if (!worldsData.has(worldName)) - worldsData.add(worldName, new JsonObject()); - JsonObject worldObject = worldsData.getAsJsonObject(worldName); - - if (!worldObject.has("automated")) - worldObject.addProperty("automated", false); - if (!worldObject.has("prompted")) - worldObject.addProperty("prompted", false); - if (!worldObject.has("days_passed")) - worldObject.addProperty("days_passed", 0); - if (!worldObject.has("since_day")) - worldObject.addProperty("since_day", 0); - if (!worldObject.has("since_backup")) - worldObject.addProperty("since_backup", 0); - if (!worldObject.has("backups")) - worldObject.add("backups", new JsonArray()); - - return worldObject; - } - - private void loadMetadata() throws FileNotFoundException { - this.metadata = JsonParser.parseReader(new FileReader(this.metadataFilePath.toFile())).getAsJsonObject(); - if (MetadataUpdater.getVersion(this.metadata).isLessThan(0, 3)) { - MetadataUpdater updater = new MetadataUpdater(this.metadata); - this.metadata = updater.update(); - saveMetadata(); - } - } - - private void saveMetadata() { + public void saveMetadata() { Rollback.LOGGER.info("Saving metadata file..."); + Path metadataFilePath = Minecraft.getInstance().getLevelSource().getBackupPath().resolve("rollbacks/rollbacks.json"); try { - this.metadata.addProperty("version", "0.3"); Files.createDirectories(this.rollbackDirectory); Files.createDirectories(this.iconsDirectory); - FileWriter writer = new FileWriter(this.metadataFilePath.toFile()); - Gson gson = new GsonBuilder()/*.setPrettyPrinting()*/.create(); - gson.toJson(this.metadata, writer); - writer.close(); + try (FileWriter writer = new FileWriter(metadataFilePath.toFile())) { + gson.toJson(this, writer); + } } catch (IOException e) { Rollback.LOGGER.error("Failed to save the metadata file!", e); throw new RuntimeException(e); @@ -330,15 +241,37 @@ private void showError(String title, String info, Throwable exception) { )); } - private void deleteNonexistentIcons(String worldName) { - JsonArray array = getWorldObject(worldName).getAsJsonArray("backups"); - for (JsonElement elm : array) { - JsonObject obj = elm.getAsJsonObject(); - if (!obj.has("icon_file")) - continue; - String iconPath = obj.get("icon_file").getAsString(); - if (!Files.isRegularFile(this.rollbackDirectory.resolve(iconPath))) - obj.remove("icon_file"); + private void deleteGhostIcons(String worldName) { + RollbackWorld world = getWorld(worldName); + for (RollbackBackup backup : world.backups) + if (backup.iconPath != null && !Files.isRegularFile(this.rollbackDirectory.resolve(backup.iconPath))) + backup.iconPath = null; + } + + public static BackupManager loadMetadata() { + Rollback.LOGGER.info("Loading metadata file..."); + Path metadataFilePath = Minecraft.getInstance().getLevelSource().getBackupPath().resolve("rollbacks/rollbacks.json"); + BackupManager backupManager; + boolean save = false; + + try (FileReader reader = new FileReader(metadataFilePath.toFile())) { + JsonObject metadata = JsonParser.parseReader(reader).getAsJsonObject(); + MetadataUpdater updater = new MetadataUpdater(metadata); + if (updater.getVersion().isOutdated()) { + metadata = updater.update(); + save = true; + } + backupManager = gson.fromJson(metadata, BackupManager.class); + } catch (FileNotFoundException e) { + Rollback.LOGGER.warn("Metadata file not found! Creating a new one..."); + backupManager = new BackupManager(); + save = true; + } catch (IOException e) { + throw new RuntimeException(e); } + + if (save) + backupManager.saveMetadata(); + return backupManager; } } diff --git a/src/main/java/ir/mehradn/rollback/util/backup/MetadataUpdater.java b/src/main/java/ir/mehradn/rollback/util/backup/MetadataUpdater.java index a3b9ef3..c0fa9e7 100644 --- a/src/main/java/ir/mehradn/rollback/util/backup/MetadataUpdater.java +++ b/src/main/java/ir/mehradn/rollback/util/backup/MetadataUpdater.java @@ -13,16 +13,16 @@ public MetadataUpdater(JsonObject metadata) { this.metadata = metadata; } - public static Version getVersion(JsonObject metadata) { - if (metadata.has("version") && metadata.get("version").isJsonPrimitive()) - return new Version(metadata.get("version").getAsString()); + public Version getVersion() { + if (this.metadata.has("version") && this.metadata.get("version").isJsonPrimitive()) + return Version.fromString(this.metadata.get("version").getAsString()); else - return new Version(0, 1); + return Version.DEFAULT_VERSION; } public JsonObject update() { Rollback.LOGGER.info("Updating the metadata..."); - Version version = getVersion(this.metadata); + Version version = getVersion(); if (version.isLessThan(0, 3)) V0_3(); return this.metadata; @@ -44,6 +44,8 @@ private void V0_3() { @Environment(EnvType.CLIENT) public static final class Version { + public static final Version LATEST_VERSION = new Version(0, 3); + public static final Version DEFAULT_VERSION = new Version(0, 1); private final int major; private final int minor; @@ -52,14 +54,28 @@ public Version(int major, int minor) { this.minor = minor; } - public Version(String version) { - String[] versionParts = version.split("\\."); - this.major = Integer.parseInt(versionParts[0]); - this.minor = Integer.parseInt(versionParts[1]); - } - public boolean isLessThan(int major, int minor) { return (this.major < major || (this.major == major && this.minor < minor)); } + + public boolean isLessThan(Version version) { + return isLessThan(version.major, version.minor); + } + + public boolean isOutdated() { + return isLessThan(LATEST_VERSION); + } + + public String toString() { + return this.major + "." + this.minor; + } + + public static Version fromString(String version) { + String[] versionParts = version.split("\\."); + return new Version( + Integer.parseInt(versionParts[0]), + Integer.parseInt(versionParts[1]) + ); + } } } diff --git a/src/main/java/ir/mehradn/rollback/util/backup/RollbackBackup.java b/src/main/java/ir/mehradn/rollback/util/backup/RollbackBackup.java index 207992d..2f1e336 100644 --- a/src/main/java/ir/mehradn/rollback/util/backup/RollbackBackup.java +++ b/src/main/java/ir/mehradn/rollback/util/backup/RollbackBackup.java @@ -1,6 +1,6 @@ package ir.mehradn.rollback.util.backup; -import com.google.gson.JsonObject; +import com.google.gson.annotations.SerializedName; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -9,63 +9,31 @@ import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.SignStyle; -import java.time.temporal.ChronoField; import java.util.Date; @Environment(EnvType.CLIENT) public class RollbackBackup { - public static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder() - .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-') - .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-') - .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('_') - .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral('-') - .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral('-') - .appendValue(ChronoField.SECOND_OF_MINUTE, 2).toFormatter(); public static final DateFormat DATE_FORMAT = new SimpleDateFormat(); - public final String worldName; - public final Path backupPath; - public final Path iconPath; - public final LocalDateTime backupTime; - public final int daysPlayed; + @SerializedName("backup_file") public Path backupPath; + @SerializedName("icon_file") public Path iconPath = null; + @SerializedName("creation_date") public LocalDateTime creationDate; + @SerializedName("days_played") public int daysPlayed; - public RollbackBackup(String directoryName, JsonObject rollbackData) { - this.worldName = directoryName; - this.backupTime = LocalDateTime.parse(rollbackData.get("creation_date").getAsString(), TIME_FORMATTER); - this.daysPlayed = rollbackData.get("days_played").getAsInt(); - this.backupPath = Path.of(rollbackData.get("backup_file").getAsString()); - if (rollbackData.has("icon_file")) - this.iconPath = Path.of(rollbackData.get("icon_file").getAsString()); - else - this.iconPath = null; - } + public RollbackBackup() { } - public RollbackBackup(String worldName, Path backupPath, Path iconPath, LocalDateTime backupTime, int daysPlayed) { - this.worldName = worldName; + public RollbackBackup(Path backupPath, Path iconPath, LocalDateTime creationDate, int daysPlayed) { this.backupPath = backupPath; this.iconPath = iconPath; - this.backupTime = backupTime; + this.creationDate = creationDate; this.daysPlayed = daysPlayed; } - public JsonObject toObject() { - JsonObject obj = new JsonObject(); - obj.addProperty("creation_date", this.backupTime.format(TIME_FORMATTER)); - obj.addProperty("days_played", this.daysPlayed); - obj.addProperty("backup_file", this.backupPath.toString()); - if (this.iconPath != null) - obj.addProperty("icon_file", this.iconPath.toString()); - return obj; - } - public String getDaysPlayedAsString() { return (this.daysPlayed == -1 ? "???" : String.valueOf(this.daysPlayed)); } public String getDateAsString() { - Date date = Date.from(this.backupTime.atZone(ZoneId.systemDefault()).toInstant()); + Date date = Date.from(this.creationDate.atZone(ZoneId.systemDefault()).toInstant()); return DATE_FORMAT.format(date); } } diff --git a/src/main/java/ir/mehradn/rollback/util/backup/RollbackWorld.java b/src/main/java/ir/mehradn/rollback/util/backup/RollbackWorld.java new file mode 100644 index 0000000..57829f7 --- /dev/null +++ b/src/main/java/ir/mehradn/rollback/util/backup/RollbackWorld.java @@ -0,0 +1,29 @@ +package ir.mehradn.rollback.util.backup; + +import com.google.gson.annotations.SerializedName; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.ArrayList; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class RollbackWorld { + @SerializedName("automated") public boolean automatedBackups = false; + @SerializedName("prompted") public boolean prompted = false; + @SerializedName("days_passed") public int daysSinceLastBackup = 0; + @SerializedName("since_day") public int ticksSinceLastMorning = 0; + @SerializedName("since_backup") public int ticksSinceLastBackup = 0; + @SerializedName("backups") public List backups = new ArrayList<>(); + + public void setPromptAnswer(boolean answer) { + this.prompted = true; + this.automatedBackups = answer; + } + + public void resetTimers() { + this.daysSinceLastBackup = 0; + this.ticksSinceLastMorning = 0; + this.ticksSinceLastBackup = 0; + } +} diff --git a/src/main/java/ir/mehradn/rollback/util/gson/LocalDateTimeAdapter.java b/src/main/java/ir/mehradn/rollback/util/gson/LocalDateTimeAdapter.java new file mode 100644 index 0000000..77bb83a --- /dev/null +++ b/src/main/java/ir/mehradn/rollback/util/gson/LocalDateTimeAdapter.java @@ -0,0 +1,32 @@ +package ir.mehradn.rollback.util.gson; + +import com.google.gson.*; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.SignStyle; +import java.time.temporal.ChronoField; + +@Environment(EnvType.CLIENT) +public class LocalDateTimeAdapter implements JsonSerializer, JsonDeserializer { + public static final LocalDateTimeAdapter INSTANCE = new LocalDateTimeAdapter(); + public static final DateTimeFormatter TIME_FORMATTER = new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-') + .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-') + .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('_') + .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral('-') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral('-') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2).toFormatter(); + + public LocalDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + return LocalDateTime.parse(json.getAsString(), TIME_FORMATTER); + } + + public JsonElement serialize(LocalDateTime obj, Type type, JsonSerializationContext context) { + return new JsonPrimitive(obj.format(TIME_FORMATTER)); + } +} diff --git a/src/main/java/ir/mehradn/rollback/util/gson/MetadataUpdaterVersionAdapter.java b/src/main/java/ir/mehradn/rollback/util/gson/MetadataUpdaterVersionAdapter.java new file mode 100644 index 0000000..097b853 --- /dev/null +++ b/src/main/java/ir/mehradn/rollback/util/gson/MetadataUpdaterVersionAdapter.java @@ -0,0 +1,21 @@ +package ir.mehradn.rollback.util.gson; + +import com.google.gson.*; +import ir.mehradn.rollback.util.backup.MetadataUpdater; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.lang.reflect.Type; + +@Environment(EnvType.CLIENT) +public class MetadataUpdaterVersionAdapter implements JsonSerializer, JsonDeserializer { + public static final MetadataUpdaterVersionAdapter INSTANCE = new MetadataUpdaterVersionAdapter(); + + public MetadataUpdater.Version deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + return MetadataUpdater.Version.fromString(json.getAsString()); + } + + public JsonElement serialize(MetadataUpdater.Version obj, Type type, JsonSerializationContext context) { + return new JsonPrimitive(obj.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/ir/mehradn/rollback/util/gson/PathAdapter.java b/src/main/java/ir/mehradn/rollback/util/gson/PathAdapter.java new file mode 100644 index 0000000..9e4a687 --- /dev/null +++ b/src/main/java/ir/mehradn/rollback/util/gson/PathAdapter.java @@ -0,0 +1,21 @@ +package ir.mehradn.rollback.util.gson; + +import com.google.gson.*; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.lang.reflect.Type; +import java.nio.file.Path; + +@Environment(EnvType.CLIENT) +public class PathAdapter implements JsonSerializer, JsonDeserializer { + public static final PathAdapter INSTANCE = new PathAdapter(); + + public Path deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + return Path.of(json.getAsString()); + } + + public JsonElement serialize(Path obj, Type type, JsonSerializationContext context) { + return new JsonPrimitive(obj.toString()); + } +} diff --git a/src/main/resources/assets/rollback/lang/en_us.json b/src/main/resources/assets/rollback/lang/en_us.json index bdd12fe..defc175 100644 --- a/src/main/resources/assets/rollback/lang/en_us.json +++ b/src/main/resources/assets/rollback/lang/en_us.json @@ -22,6 +22,7 @@ "rollback.narrator.selectCurrentSave": "Current save, last played: %s", "rollback.command.list.title": "Backups:", "rollback.command.list.noBackups": "There are no backups available for this world", + "rollback.command.playerOnly": "This command can only be used by a player", "rollback.command.unavailable": "This command is only available to the server host", "rollback.midnightconfig.title": "Rollback Options", "rollback.midnightconfig.backupsPerWorld": "Maximum Backups Per World:",