fix fix fix
This commit is contained in:
1 parent
394a4d986d
commit
48bf045825
16 files changed
+172
-59
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user