fix fix fix

This commit is contained in:
iceBear67 committed 2025-10-01 16:59:17 +08:00
1 parent 394a4d986d
commit 48bf045825
16 files changed
+172 -59

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@ public record Backup(
long sizeTotal
) {
public Text toText(){
return Text.of("sizeTotal: "+((sizeTotal / 1024.0) / 1024.0) + "GiB, "+
return Text.of("sizeTotal: "+Math.round(((sizeTotal / 1024.0) / 1024.0) * 100) * 0.01 + "MiB, "+
(incremental ? "incremental" :"") + ", type: "+type+"."
);
}
@@ -7,6 +7,7 @@ import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.ib67.sfcraft.bundler.BundleReader;
import lombok.extern.log4j.Log4j2;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
@@ -15,7 +16,10 @@ import net.minecraft.text.ClickEvent;
import net.minecraft.text.Text;
import java.awt.*;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@@ -33,10 +37,10 @@ public class BackupCommands {
" -- Show this message",
" /backup list ",
" -- List backups",
" /backup restore <backupName>",
" /backup restore <backupKey>",
" -- Restore the backup on next server restart.",
" Can be cancelled by /backup cancelrestore",
" /backup delete <backupName>",
" /backup delete <backupKey>",
" -- Delete a backup. This does not ask for a confirmation.",
" /backup create <incremental: true/false> <subject / \"@all\">",
" -- Create a backup.",
@@ -61,11 +65,11 @@ public class BackupCommands {
.then(literal("list").executes(this::cmdList))
.then(literal("cancelrestore").executes(this::cmdCancelRestore))
.then(literal("delete")
.then(argument("backupName", StringArgumentType.greedyString())
.then(argument("backupKey", StringArgumentType.greedyString())
.suggests(this::suggestBackups)
.executes(this::cmdDelete)))
.then(literal("restore")
.then(argument("backupName", StringArgumentType.greedyString())
.then(argument("backupKey", StringArgumentType.greedyString())
.suggests(this::suggestBackups)
.executes(this::cmdRestore)))
.then(literal("create")
@@ -128,19 +132,49 @@ public class BackupCommands {
s.sendMessage(Text.of("TIP: Use `/backup cancelrestore` to cancel that request."));
return 0;
}
var backupName = StringArgumentType.getString(context, "backupName");
var backupKey = StringArgumentType.getString(context, "backupKey");
var backup = mod.tracker.getTrackedBackups().get(backupKey);
if (backup == null) {
s.sendMessage(Text.of("Invalid backup " + backupKey + ". Note: Use backupKey instead of backupName!"));
return 0;
}
restoreIssued = () -> {
//todo implementation
log.warn("EXTRACTING BACKUP " + backup + " TO " + backup.from());
log.warn(" == STEP 1 == Make a backup for the destination.");
var destination = Path.of(backup.from());
var movedDst = destination.resolveSibling(destination.getFileName().toString()+"_bak_"+System.currentTimeMillis());
try{
Files.move(destination, movedDst);
log.info("{} has been moved to {}", destination, movedDst);
} catch (IOException e) {
log.error(e);
log.error("CANNOT MOVE "+destination+" TO "+movedDst);
log.error("BACKUP INTERRUPTED. Here are some tips helping you out:");
log.error(" -- A. Merge already moved files");
log.error(" Try this command in your server directory: (linux)");
log.error(" $ cp ./"+movedDst+"/* ./"+destination);
log.error(" -- B. Remove them all and use external unbundler tools");
log.error(" You may want to copy them elsewhere first (see kind A)");
log.error(" $ rm -r ./"+destination+" ./"+movedDst);
log.error(" Visit https://github.com/saltedfishclub/justbackup for the usage of bundler tools");
log.error(" ------- ERROR END -------");
return;
}
log.info(" == STEP 2 == Recovering backup "+backup);
mod.tracker.recoverBackup(backup, destination);
log.info("Backup has been recovered.");
};
s.sendMessage(Text.of("Backup restoration task has been scheduled!"));
s.sendMessage(Text.of("Restart your server to take changes."));
return Command.SINGLE_SUCCESS;
}
private int cmdDelete(CommandContext<ServerCommandSource> context) {
var backupName = StringArgumentType.getString(context, "backupName");
var backup = mod.tracker.getTrackedBackups().get(backupName);
var backupKey = StringArgumentType.getString(context, "backupKey");
var backup = mod.tracker.getTrackedBackups().get(backupKey);
var source = context.getSource();
if (backup == null) {
source.sendMessage(Text.of("Invalid backup. No backups are named '" + backupName + "'"));
source.sendMessage(Text.of("Invalid backup. No backups are named '" + backupKey + "'"));
return 0;
}
try {
@@ -194,6 +228,8 @@ public class BackupCommands {
for (String s : helpMessage) {
if (s.startsWith(" /")) {
source.sendMessage(Text.literal(s).withColor(Color.CYAN.getRGB()));
} else {
source.sendMessage(Text.of(s));
}
}
return Command.SINGLE_SUCCESS;
@@ -33,19 +33,14 @@ public record BackupWorker(
if (server == null) {
throw new IllegalStateException("MinecraftServer is not initialized yet, not doing backup");
}
int i = 0;
server.saveAll(true, true, false);
while (!BACKUP_LOCK.compareAndSet(IOState.IDLE, IOState.BACKUP)) {
log.info("Server is saving world while performing backup! waiting...");
i++;
Thread.sleep(2000);
}
// performing backup is now true
var tmpBundle = tmpDir.resolve("backup_" + subject + "_" + System.currentTimeMillis() + ".swb.zst");
var tmpBundle = tmpDir.resolve("backup_" + subject.name() + "_" + System.currentTimeMillis() + ".swb.zst");
try {
if (i == 0) {
// not saved yet.
server.saveAll(true, true, false);
}
// tmp file path
log.info("Creating bundle {}", tmpBundle);
BundleWriter.createBundle(tmpBundle, subject.changedFiles(), config);
@@ -1,8 +1,10 @@
package io.ib67.sfcraft;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -10,6 +12,7 @@ import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Log4j2
public class FileWatcherThread extends Thread {
protected final Map<Path, String> watchSubject;
protected final BiConsumer<String, Path> eventListener;
@@ -20,17 +23,24 @@ public class FileWatcherThread extends Thread {
) {
this.watchSubject = watchSubject.entrySet()
.stream()
.flatMap(it -> walkThrough(it.getValue()).map(r -> Map.entry(r, it.getKey())))
// todo support for nonexist dir
.flatMap(it -> walkThrough(it.getValue()).map(r -> Map.entry(r.toAbsolutePath(), it.getKey())))
.filter(it->Files.isDirectory(it.getKey()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
this.eventListener = eventListener;
setName("JustBackup/FileWatcher");
setDaemon(true);
}
@SneakyThrows
private static Stream<Path> walkThrough(Path path) {
if(Files.notExists(path)){
log.error("{} not found. Creating directory for it..", path);
Files.createDirectories(path);
}
return Files.walk(path);
}
@@ -38,12 +48,14 @@ public class FileWatcherThread extends Thread {
@SneakyThrows
public void run() {
try (var watcher = FileSystems.getDefault().newWatchService()) {
var keyToParent = new HashMap<WatchKey, Path>();
for (Path value : watchSubject.keySet()) {
value.register(
var key = value.register(
watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY
);
keyToParent.put(key, value);
}
for (; ; ) {
var key = watcher.poll(10, TimeUnit.SECONDS);
@@ -53,17 +65,25 @@ public class FileWatcherThread extends Thread {
var kind = ev.kind();
if (kind == StandardWatchEventKinds.OVERFLOW)
continue;
var path = ((WatchEvent<Path>) ev).context();
var subject = Objects.requireNonNull(watchSubject.get(path.getParent()));
var parent = keyToParent.get(key);
var subject = watchSubject.get(parent);
if(subject == null) {
log.warn("Ignoring changes in "+parent);
continue;
}
var path = parent.resolve(((WatchEvent<Path>) ev).context());
if (Files.isRegularFile(path)) {
eventListener.accept(subject, path.getParent());
eventListener.accept(subject, parent);
} else if (Files.isDirectory(path) && kind == StandardWatchEventKinds.ENTRY_CREATE) {
path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);
var nK = path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);
watchSubject.put(path, subject);
keyToParent.put(nK, path);
}
}
key.reset();
}
}catch (InterruptedException interruptedException){
log.info("FileWatcherThread is quitting!");
}
}
}
@@ -30,13 +30,13 @@ 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 java.util.stream.Collectors;
public class JustBackupMod implements ModInitializer {
public static final String MOD_ID = "justbackup";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
protected final Map<String, BackupSubject> backupSubjects = new HashMap<>();
protected int lastBackupServerTicks;
protected volatile MinecraftServer server;
protected volatile boolean suspend;
protected byte[] zstdDict;
@@ -63,7 +63,7 @@ public class JustBackupMod implements ModInitializer {
config.backupSubjects().forEach((k, v) -> {
var subjectPath = Path.of(v);
if (Files.notExists(subjectPath)) {
LOGGER.error("Backup subject {} does not exist.", v);
LOGGER.error("Backup _subject {} does not exist.", v);
return;
}
backupSubjects.put(k, new BackupSubject(k, subjectPath, new ArrayList<>()));
@@ -93,9 +93,11 @@ public class JustBackupMod implements ModInitializer {
watcherThread.start();
}
private void handleFileChanges(String subject, Path path) {
private void handleFileChanges(String _subject, Path path) {
synchronized (backupSubjects) {
backupSubjects.get(subject).changedFiles().add(path);
var subject = backupSubjects.get(_subject);
if (subject != null)
subject.changedFiles().add(path); // check null
}
}
@@ -108,6 +110,12 @@ public class JustBackupMod implements ModInitializer {
backupAll(false);
}
scheduledBackupExecutor.scheduleAtFixedRate(() -> {
if (lastBackupServerTicks > 0 && lastBackupServerTicks == server.getTicks()) {
// the server is idling
LOGGER.debug("Server is idling, not backing up.");
return;
}
lastBackupServerTicks = server.getTicks();
if (!suspend) backupAll(config.incremental()).thenAccept(it -> {
LOGGER.info("Backup success!");
it.forEach((k, v) -> LOGGER.info(k + ": " + v));
@@ -125,6 +133,7 @@ public class JustBackupMod implements ModInitializer {
var command = new BackupCommands(this);
CommandRegistrationCallback.EVENT.register(command::registerCommand);
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
LOGGER.info("Shutting down the executor");
scheduledBackupExecutor.shutdown();
if (command.restoreIssued != null) {
command.restoreIssued.run();
@@ -134,6 +143,7 @@ public class JustBackupMod implements ModInitializer {
}
public CompletableFuture<Map<String, Backup>> backupAll(boolean incremental) {
if (backupSubjects.isEmpty()) return CompletableFuture.completedFuture(Map.of());
var map = new HashMap<String, CompletableFuture<Backup>>();
server.getPlayerManager().broadcast(
Text.literal(" BACKUP >> ").withColor(Color.RED.getRGB()).styled(it -> it.withBold(true))
@@ -146,7 +156,11 @@ public class JustBackupMod implements ModInitializer {
return CompletableFuture.allOf(map.values().toArray(new CompletableFuture[0]))
.thenApply(it -> {
var _map = new HashMap<String, Backup>();
map.forEach((k, v) -> _map.put(k, v.join()));
map.forEach((k, v) -> {
var result = v.join();
LOGGER.info("Backup information of {}: {}", k, result);
_map.put(k, result);
});
return _map;
});
}
@@ -154,15 +168,18 @@ public class JustBackupMod implements ModInitializer {
public CompletableFuture<Backup> issueBackup(String subjectName, boolean incremental) {
var subject = backupSubjects.get(subjectName);
if (subject == null)
return CompletableFuture.failedFuture(new IllegalArgumentException("Unknown backup subject: " + subjectName));
return CompletableFuture.failedFuture(new IllegalArgumentException("Unknown backup _subject: " + subjectName));
if (!incremental) {
try (var s = Files.walk(subject.root())) {
subject = new BackupSubject(subject.name(), subject.root(), s.filter(Files::isRegularFile).toList());
subject = new BackupSubject(subject.name(), subject.root(), s.filter(Files::isRegularFile)
.filter(it -> !it.getFileName().toString().equals("session.lock"))
.toList());
} catch (IOException e) {
return CompletableFuture.failedFuture(new IllegalArgumentException("Cannot perform full backup for " + subjectName, e));
}
}
var finalSubject = subject;
if (config.allowGunzip()) LOGGER.info("Chunk reassembler is enabled!");
var worker = new BackupWorker(server, tracker, Path.of(config.temporaryBackupDir()),
subject, c ->
c.allowGunzip(config.allowGunzip())
@@ -26,14 +26,15 @@ public class LocalBackupStrategy implements BackupStrategy {
@Override
@SneakyThrows
public Backup createBackup(String subject, Path from, Path pathToBundle) {
var parent = backupParentRoot.resolve(subject);
var result = new Backup(
pathToBundle.getFileName().toString(),
pathToBundle.getFileName().toString(),
Path.of(subject).resolve(pathToBundle.getFileName()).normalize().toString(),
"local", from.toString(), false, Files.size(pathToBundle));
var parent = backupParentRoot.resolve(subject);
if(Files.notExists(parent)) {
Files.createDirectories(parent);
}
//todo new name
Files.move(pathToBundle, parent.resolve(pathToBundle.getFileName()));
return result;
}
@@ -3,7 +3,6 @@
"package": "io.ib67.sfcraft.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"LevelStorageAccessor",
"MixinMinecraftServer",
"MixinRegionBasedStorage"
],