parent
6707e813b2
commit
81c5cd04cb
|
@ -36,16 +36,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class Globals {
|
public class Globals {
|
||||||
public static final Globals INSTANCE = new Globals();
|
public static final Globals INSTANCE = new Globals();
|
||||||
public final static DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
|
|
||||||
|
|
||||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||||
|
public final static DateTimeFormatter defaultDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
|
||||||
|
|
||||||
private ExecutorService executorService = Executors.newSingleThreadExecutor();
|
private ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||||
public final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true);
|
public final AtomicBoolean globalShutdownBackupFlag = new AtomicBoolean(true);
|
||||||
|
|
||||||
public boolean disableWatchdog = false;
|
public boolean disableWatchdog = false;
|
||||||
private boolean disableTMPFiles = false;
|
private boolean disableTMPFiles = false;
|
||||||
|
|
||||||
private AwaitThread restoreAwaitThread = null;
|
private AwaitThread restoreAwaitThread = null;
|
||||||
private Path lockedPath = null;
|
private Path lockedPath = null;
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class TextileBackup implements ModInitializer {
|
||||||
|
|
||||||
ConfigHelper.updateInstance(AutoConfig.register(ConfigPOJO.class, JanksonConfigSerializer::new));
|
ConfigHelper.updateInstance(AutoConfig.register(ConfigPOJO.class, JanksonConfigSerializer::new));
|
||||||
|
|
||||||
ServerTickEvents.END_SERVER_TICK.register(new BackupScheduler()::tick);
|
ServerTickEvents.END_SERVER_TICK.register(BackupScheduler::tick);
|
||||||
|
|
||||||
//Restart Executor Service in single-player
|
//Restart Executor Service in single-player
|
||||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
|
||||||
|
@ -63,6 +63,7 @@ public class TextileBackup implements ModInitializer {
|
||||||
Globals.INSTANCE.updateTMPFSFlag(server);
|
Globals.INSTANCE.updateTMPFSFlag(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Wait 60s for already submited backups to finish. After that kill the bastards and run the one last if required
|
||||||
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
|
ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
|
||||||
Globals.INSTANCE.shutdownQueueExecutor(60000);
|
Globals.INSTANCE.shutdownQueueExecutor(60000);
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,13 @@
|
||||||
|
|
||||||
package net.szum123321.textile_backup.core;
|
package net.szum123321.textile_backup.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing possible sources of action
|
||||||
|
*/
|
||||||
public enum ActionInitiator {
|
public enum ActionInitiator {
|
||||||
Player("Player", "by"),
|
Player("Player", "by"),
|
||||||
ServerConsole("Server Console", "from"),
|
ServerConsole("Server Console", "from"), //some/ting typed a command and it was not a player (command blocks and server console count)
|
||||||
Timer("Timer", "by"),
|
Timer("Timer", "by"), //a.k.a scheduler
|
||||||
Shutdown("Server Shutdown", "by"),
|
Shutdown("Server Shutdown", "by"),
|
||||||
Restore("Backup Restoration", "because of"),
|
Restore("Backup Restoration", "because of"),
|
||||||
Null("Null (That shouldn't have happened)", "form");
|
Null("Null (That shouldn't have happened)", "form");
|
||||||
|
|
|
@ -33,6 +33,9 @@ import java.time.ZoneOffset;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of utility used for removing old backups
|
||||||
|
*/
|
||||||
public class Cleanup {
|
public class Cleanup {
|
||||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||||
|
@ -55,7 +58,7 @@ public class Cleanup {
|
||||||
}
|
}
|
||||||
|
|
||||||
final int noToKeep = config.get().backupsToKeep > 0 ? config.get().backupsToKeep : Integer.MAX_VALUE;
|
final int noToKeep = config.get().backupsToKeep > 0 ? config.get().backupsToKeep : Integer.MAX_VALUE;
|
||||||
final long maxSize = config.get().maxSize > 0 ? config.get().maxSize * 1024: Long.MAX_VALUE;
|
final long maxSize = config.get().maxSize > 0 ? config.get().maxSize * 1024: Long.MAX_VALUE; //max number of bytes to keep
|
||||||
|
|
||||||
long[] counts = count(root);
|
long[] counts = count(root);
|
||||||
long n = counts[0], size = counts[1];
|
long n = counts[0], size = counts[1];
|
||||||
|
|
|
@ -40,6 +40,9 @@ import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class parses backup files, extracting its creation time, format and possibly comment
|
||||||
|
*/
|
||||||
public class RestoreableFile implements Comparable<RestoreableFile> {
|
public class RestoreableFile implements Comparable<RestoreableFile> {
|
||||||
private final Path file;
|
private final Path file;
|
||||||
private final ConfigPOJO.ArchiveFormat archiveFormat;
|
private final ConfigPOJO.ArchiveFormat archiveFormat;
|
||||||
|
@ -53,6 +56,7 @@ public class RestoreableFile implements Comparable<RestoreableFile> {
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//removes repetition of the files stream thingy with awfully large lambdas
|
||||||
public static <T> T applyOnFiles(Path root, T def, Consumer<IOException> errorConsumer, Function<Stream<RestoreableFile>, T> streamConsumer) {
|
public static <T> T applyOnFiles(Path root, T def, Consumer<IOException> errorConsumer, Function<Stream<RestoreableFile>, T> streamConsumer) {
|
||||||
try (Stream<Path> stream = Files.list(root)) {
|
try (Stream<Path> stream = Files.list(root)) {
|
||||||
return streamConsumer.apply(stream.flatMap(f -> RestoreableFile.build(f).stream()));
|
return streamConsumer.apply(stream.flatMap(f -> RestoreableFile.build(f).stream()));
|
||||||
|
|
|
@ -65,7 +65,6 @@ public class Utilities {
|
||||||
.getWorldDirectory(World.OVERWORLD);
|
.getWorldDirectory(World.OVERWORLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void deleteDirectory(Path path) throws IOException {
|
public static void deleteDirectory(Path path) throws IOException {
|
||||||
Files.walkFileTree(path, new SimplePathVisitor() {
|
Files.walkFileTree(path, new SimplePathVisitor() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,12 +21,9 @@ package net.szum123321.textile_backup.core.create;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
import net.minecraft.server.command.ServerCommandSource;
|
||||||
import net.minecraft.util.Util;
|
|
||||||
import net.szum123321.textile_backup.core.ActionInitiator;
|
import net.szum123321.textile_backup.core.ActionInitiator;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public record BackupContext(@NotNull MinecraftServer server,
|
public record BackupContext(@NotNull MinecraftServer server,
|
||||||
ServerCommandSource commandSource,
|
ServerCommandSource commandSource,
|
||||||
ActionInitiator initiator,
|
ActionInitiator initiator,
|
||||||
|
@ -103,8 +100,7 @@ public record BackupContext(@NotNull MinecraftServer server,
|
||||||
|
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
if (commandSource != null) setServer(commandSource.getServer());
|
if (commandSource != null) setServer(commandSource.getServer());
|
||||||
else
|
else throw new RuntimeException("Neither MinecraftServer or ServerCommandSource were provided!");
|
||||||
throw new RuntimeException("Neither MinecraftServer or ServerCommandSource were provided!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BackupContext(server, commandSource, initiator, save, comment);
|
return new BackupContext(server, commandSource, initiator, save, comment);
|
||||||
|
|
|
@ -25,24 +25,32 @@ import net.szum123321.textile_backup.core.ActionInitiator;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs backup on a preset interval
|
||||||
|
* <br>
|
||||||
|
* The important thing to note: <br>
|
||||||
|
* In the case that <code>doBackupsOnEmptyServer == false</code> and there have been made backups with players online,
|
||||||
|
* then everyone left the backup that was scheduled with player is still going to run. So it might appear as though there
|
||||||
|
* has been made backup with no players online despite the config. This is the expected behaviour
|
||||||
|
* <br>
|
||||||
|
* Furthermore, it uses system time
|
||||||
|
*/
|
||||||
public class BackupScheduler {
|
public class BackupScheduler {
|
||||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||||
|
|
||||||
private boolean scheduled;
|
//Scheduled flag tells whether we have decided to run another backup
|
||||||
private long nextBackup;
|
private static boolean scheduled = false;
|
||||||
|
private static long nextBackup = - 1;
|
||||||
|
|
||||||
public BackupScheduler() {
|
public static void tick(MinecraftServer server) {
|
||||||
scheduled = false;
|
|
||||||
nextBackup = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tick(MinecraftServer server) {
|
|
||||||
if(config.get().backupInterval < 1) return;
|
if(config.get().backupInterval < 1) return;
|
||||||
long now = Instant.now().getEpochSecond();
|
long now = Instant.now().getEpochSecond();
|
||||||
|
|
||||||
if(config.get().doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
if(config.get().doBackupsOnEmptyServer || server.getPlayerManager().getCurrentPlayerCount() > 0) {
|
||||||
|
//Either just run backup with no one playing or there's at least one player
|
||||||
if(scheduled) {
|
if(scheduled) {
|
||||||
if(nextBackup <= now) {
|
if(nextBackup <= now) {
|
||||||
|
//It's time to run
|
||||||
Globals.INSTANCE.getQueueExecutor().submit(
|
Globals.INSTANCE.getQueueExecutor().submit(
|
||||||
MakeBackupRunnableFactory.create(
|
MakeBackupRunnableFactory.create(
|
||||||
BackupContext.Builder
|
BackupContext.Builder
|
||||||
|
@ -57,11 +65,15 @@ public class BackupScheduler {
|
||||||
nextBackup = now + config.get().backupInterval;
|
nextBackup = now + config.get().backupInterval;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//Either server just started or a new player joined after the last backup has finished
|
||||||
|
//So let's schedule one some time from now
|
||||||
nextBackup = now + config.get().backupInterval;
|
nextBackup = now + config.get().backupInterval;
|
||||||
scheduled = true;
|
scheduled = true;
|
||||||
}
|
}
|
||||||
} else if(!config.get().doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
} else if(!config.get().doBackupsOnEmptyServer && server.getPlayerManager().getCurrentPlayerCount() == 0) {
|
||||||
|
//Do the final backup. No one's on-line and doBackupsOnEmptyServer == false
|
||||||
if(scheduled && nextBackup <= now) {
|
if(scheduled && nextBackup <= now) {
|
||||||
|
//Verify we hadn't done the final one and its time to do so
|
||||||
Globals.INSTANCE.getQueueExecutor().submit(
|
Globals.INSTANCE.getQueueExecutor().submit(
|
||||||
MakeBackupRunnableFactory.create(
|
MakeBackupRunnableFactory.create(
|
||||||
BackupContext.Builder
|
BackupContext.Builder
|
||||||
|
|
|
@ -37,6 +37,9 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual object responsible for creating the backup
|
||||||
|
*/
|
||||||
public class MakeBackupRunnable implements Runnable {
|
public class MakeBackupRunnable implements Runnable {
|
||||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||||
|
@ -87,7 +90,7 @@ public class MakeBackupRunnable implements Runnable {
|
||||||
log.trace("Using PARALLEL Zip Compressor. Threads: {}", coreCount);
|
log.trace("Using PARALLEL Zip Compressor. Threads: {}", coreCount);
|
||||||
} else {
|
} else {
|
||||||
ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
|
ZipCompressor.getInstance().createArchive(world, outFile, context, coreCount);
|
||||||
log.trace("Using REGULAR Zip Compressor. Threads: {}");
|
log.trace("Using REGULAR Zip Compressor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case BZIP2 -> ParallelBZip2Compressor.getInstance().createArchive(world, outFile, context, coreCount);
|
case BZIP2 -> ParallelBZip2Compressor.getInstance().createArchive(world, outFile, context, coreCount);
|
||||||
|
|
|
@ -33,6 +33,9 @@ import java.time.Instant;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic abstract class representing directory compressor
|
||||||
|
*/
|
||||||
public abstract class AbstractCompressor {
|
public abstract class AbstractCompressor {
|
||||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||||
|
|
||||||
|
@ -88,7 +91,7 @@ public abstract class AbstractCompressor {
|
||||||
protected abstract void addEntry(Path file, String entryName, OutputStream arc) throws IOException;
|
protected abstract void addEntry(Path file, String entryName, OutputStream arc) throws IOException;
|
||||||
|
|
||||||
protected void finish(OutputStream arc) throws InterruptedException, ExecutionException, IOException {
|
protected void finish(OutputStream arc) throws InterruptedException, ExecutionException, IOException {
|
||||||
//Basically this function is only needed for the ParallelZipCompressor to write out ParallelScatterZipCreator
|
//This function is only needed for the ParallelZipCompressor to write out ParallelScatterZipCreator
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void close() {
|
protected void close() {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
//TODO: Verify backup's validity?
|
||||||
public class RestoreBackupRunnable implements Runnable {
|
public class RestoreBackupRunnable implements Runnable {
|
||||||
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
private final static TextileLogger log = new TextileLogger(TextileBackup.MOD_NAME);
|
||||||
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
private final static ConfigHelper config = ConfigHelper.INSTANCE;
|
||||||
|
|
|
@ -25,6 +25,10 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This mixin should numb Watchdog while a backup runs.
|
||||||
|
* If works as intended solves issues with watchdog errors
|
||||||
|
*/
|
||||||
@Mixin(DedicatedServerWatchdog.class)
|
@Mixin(DedicatedServerWatchdog.class)
|
||||||
public class DedicatedServerWatchdogMixin {
|
public class DedicatedServerWatchdogMixin {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue