Refactor backup system and migrate from submodule to shadow dependencies
- Remove s3-lite submodule and replace with AWS SDK and shadow plugin - Implement comprehensive backup commands with list, delete, create, restore, and suspend functionality - Add bundle format with compression support using Zstd and gunzip optimization - Implement local and S3 backup strategies with configurable options - Add backup tracking system with JSON persistence - Improve world directory handling and file exclusion logic - Add standalone bundler tool for bundle creation/extraction - Update Gradle configuration with shadow jar support and dependency management - Enhance configuration system with additional options for compression and temporary directories
This commit is contained in:
1 parent
6a2642ab93
commit
fb42be8155
21 files changed
+746
-183
No files matched your search
@@ -2,36 +2,58 @@ package io.ib67.sfcraft;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import io.ib67.sfcraft.config.JustBackupConfig;
|
||||
import io.ib67.sfcraft.config.StorageOption;
|
||||
import io.ib67.sfcraft.config.serializer.StorageOptionSerializer;
|
||||
import io.ib67.sfcraft.mixin.LevelStorageAccessor;
|
||||
import io.ib67.sfcraft.mixin.MixinMinecraftServer;
|
||||
import io.ib67.sfcraft.strategy.BackupStrategy;
|
||||
import io.ib67.sfcraft.strategy.BackupTracker;
|
||||
import io.ib67.sfcraft.strategy.LocalBackupStrategy;
|
||||
import io.ib67.sfcraft.strategy.S3BackupStrategy;
|
||||
import lombok.SneakyThrows;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.text.Text;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static net.minecraft.server.command.CommandManager.argument;
|
||||
import static net.minecraft.server.command.CommandManager.literal;
|
||||
|
||||
public class JustBackupMod implements ModInitializer {
|
||||
public static final String MOD_ID = "justbackup";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
/**
|
||||
* This state indicates:
|
||||
* 1. A minecraft auto-save is in-progress.
|
||||
* 2. The backup is in-progress
|
||||
*/
|
||||
public static final AtomicBoolean PERFORMING_BACKUP = new AtomicBoolean(false);
|
||||
public static final Gson GSON = new GsonBuilder().setPrettyPrinting()
|
||||
.registerTypeAdapter(StorageOption.class, new StorageOptionSerializer(
|
||||
@@ -41,38 +63,95 @@ public class JustBackupMod implements ModInitializer {
|
||||
.create();
|
||||
protected ScheduledExecutorService scheduledBackupExecutor;
|
||||
protected JustBackupConfig config;
|
||||
protected BackupStrategy strategy;
|
||||
protected BackupTracker tracker;
|
||||
protected Path backupIndexPath;
|
||||
protected volatile MinecraftServer server;
|
||||
protected volatile boolean suspend;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onInitialize() {
|
||||
LOGGER.info("Loading backup configuration");
|
||||
backupIndexPath = Path.of("config").resolve("just_backup_index.json");
|
||||
var configPath = Path.of("config").resolve("just_backup.json");
|
||||
if (Files.notExists(configPath.getParent())) {
|
||||
if (Files.notExists(configPath.getParent()))
|
||||
Files.createDirectories(configPath.getParent());
|
||||
}
|
||||
if (Files.notExists(configPath)) {
|
||||
if (Files.notExists(configPath))
|
||||
saveConfig(configPath);
|
||||
}
|
||||
if (Files.notExists(backupIndexPath))
|
||||
Files.createFile(backupIndexPath);
|
||||
|
||||
config = GSON.fromJson(Files.readString(configPath), JustBackupConfig.class);
|
||||
this.strategy = createStrategy();
|
||||
var strategy = createStrategy();
|
||||
this.tracker = new BackupTracker(strategy, GSON.fromJson(Files.readString(backupIndexPath), new TypeToken<>() {
|
||||
}));
|
||||
LOGGER.info("Configuration has been loaded. Using storage option: {}", config.option().type());
|
||||
scheduledBackupExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
registerHandlers();
|
||||
}
|
||||
|
||||
private void registerHandlers() {
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||
LOGGER.info("Launching background threads...");
|
||||
this.server = server;
|
||||
scheduledBackupExecutor.scheduleAtFixedRate(new BackupWorker(server, strategy),
|
||||
scheduledBackupExecutor.scheduleAtFixedRate(this::issueBackup,
|
||||
config.backupIntervalMinutes(), config.backupIntervalMinutes(), TimeUnit.MINUTES);
|
||||
});
|
||||
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
|
||||
try {
|
||||
Files.deleteIfExists(Path.of(config.temporaryBackupDir()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommand);
|
||||
}
|
||||
|
||||
public CompletableFuture<Backup> issueBackup() {
|
||||
if (suspend) {
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Backup is temporarily disabled"));
|
||||
}
|
||||
server.getPlayerManager().broadcast(Text.literal("The server will begin the backup shortly, you may experience some lag."), false);
|
||||
var worker = new BackupWorker(server, tracker, config);
|
||||
return CompletableFuture.supplyAsync(worker, scheduledBackupExecutor).whenComplete(this::onBackupComplete);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void onBackupComplete(@UnknownNullability Backup backup, @UnknownNullability Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
server.getPlayerManager().broadcast(Text.literal("Backup failed. For administrators, please check your server console."), false);
|
||||
throwable.printStackTrace();
|
||||
} else {
|
||||
Files.writeString(backupIndexPath, GSON.toJson(tracker.getTrackedBackups()));
|
||||
server.getPlayerManager().broadcast(Text.literal("Backup success. " + backup), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(
|
||||
CommandDispatcher<ServerCommandSource> dispatcher,
|
||||
CommandRegistryAccess registry, CommandManager.RegistrationEnvironment env) {
|
||||
var commandHandler = new BackupCommands(this);
|
||||
dispatcher.register(literal("backup")
|
||||
.requires(it -> it.hasPermissionLevel(3))
|
||||
.then(literal("help").executes(commandHandler::cmdHelp))
|
||||
.then(literal("list").executes(commandHandler::cmdList))
|
||||
.then(literal("cancelrestore").executes(commandHandler::cmdCancelRestore))
|
||||
.then(literal("delete")
|
||||
.then(argument("backupName", StringArgumentType.greedyString())
|
||||
.executes(commandHandler::cmdDelete)))
|
||||
.then(literal("restore")
|
||||
.then(argument("backupName", StringArgumentType.greedyString())
|
||||
.executes(commandHandler::cmdRestore)))
|
||||
.then(literal("create").executes(commandHandler::cmdIssueBackup))
|
||||
.then(literal("suspend").executes(commandHandler::cmdSuspend))
|
||||
);
|
||||
}
|
||||
|
||||
private BackupStrategy createStrategy() {
|
||||
switch (config.option()) {
|
||||
case StorageOption.Local local -> {
|
||||
|
||||
}
|
||||
}
|
||||
return switch (config.option()) {
|
||||
case StorageOption.Local local -> new LocalBackupStrategy(local);
|
||||
case StorageOption.S3 s3 -> new S3BackupStrategy(s3);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -83,8 +162,14 @@ public class JustBackupMod implements ModInitializer {
|
||||
60,
|
||||
5,
|
||||
true,
|
||||
new StorageOption.Local("backups")
|
||||
true,
|
||||
"./.backup_cache",
|
||||
new StorageOption.Local("backups", 0)
|
||||
);
|
||||
var path = Path.of(cfg.temporaryBackupDir());
|
||||
if (Files.notExists(path)) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
Files.writeString(configPath, GSON.toJson(cfg));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user