From 7444c2b1f281b5b147717ba2a2ed6798c66a057b Mon Sep 17 00:00:00 2001 From: Keuin Date: Mon, 27 Apr 2020 17:42:07 +0800 Subject: Resized the logo to reduce plugin size. Refactored code for a better implementation of async and blocking task. --- .../java/com/keuin/kbackupfabric/KBCommands.java | 31 ++++-- .../com/keuin/kbackupfabric/KBPluginEvents.java | 2 +- .../operation/AbstractConfirmableOperation.java | 20 ---- .../kbackupfabric/operation/BackupOperation.java | 105 ++++++++++++++++++++ .../kbackupfabric/operation/DeleteOperation.java | 27 +++-- .../kbackupfabric/operation/RestoreOperation.java | 92 +++++++++++++++-- .../abstracts/AbstractAsyncOperation.java | 53 ++++++++++ .../abstracts/AbstractBlockingOperation.java | 58 +++++++++++ .../abstracts/AbstractConfirmableOperation.java | 11 +++ .../abstracts/AbstractSerializedOperation.java | 12 +++ .../abstracts/InvokableAsyncBlockingOperation.java | 66 +++++++++++++ .../abstracts/InvokableAsyncOperation.java | 32 ++++++ .../abstracts/InvokableBlockingOperation.java | 39 ++++++++ .../operation/abstracts/InvokableOperation.java | 9 ++ .../operation/abstracts/i/Blocking.java | 5 + .../operation/abstracts/i/Invokable.java | 5 + .../keuin/kbackupfabric/worker/BackupWorker.java | 109 --------------------- .../keuin/kbackupfabric/worker/RestoreWorker.java | 95 ------------------ 18 files changed, 518 insertions(+), 253 deletions(-) delete mode 100644 src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java delete mode 100644 src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java delete mode 100644 src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java (limited to 'src/main/java') diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java index 8a1a7cc..eb7e000 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java +++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java @@ -2,12 +2,14 @@ package com.keuin.kbackupfabric; import com.keuin.kbackupfabric.metadata.BackupMetadata; import com.keuin.kbackupfabric.metadata.MetadataHolder; -import com.keuin.kbackupfabric.operation.AbstractConfirmableOperation; +import com.keuin.kbackupfabric.operation.BackupOperation; +import com.keuin.kbackupfabric.operation.DeleteOperation; +import com.keuin.kbackupfabric.operation.RestoreOperation; +import com.keuin.kbackupfabric.operation.abstracts.i.Invokable; import com.keuin.kbackupfabric.util.BackupFilesystemUtil; import com.keuin.kbackupfabric.util.BackupNameSuggestionProvider; import com.keuin.kbackupfabric.util.BackupNameTimeFormatter; import com.keuin.kbackupfabric.util.PrintUtil; -import com.keuin.kbackupfabric.worker.BackupWorker; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.MinecraftServer; @@ -31,7 +33,7 @@ public final class KBCommands { //private static final Logger LOGGER = LogManager.getLogger(); private static final List backupNameList = new ArrayList<>(); // index -> backupName - private static AbstractConfirmableOperation pendingOperation = null; + private static Invokable pendingOperation = null; /** * Print the help menu. @@ -130,7 +132,8 @@ public final class KBCommands { } // Update pending task - pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName); + //pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName); + pendingOperation = new DeleteOperation(context, backupName); msgWarn(context, String.format("DELETION WARNING: The deletion is irreversible! You will lose the backup %s permanently. Use /kb confirm to start or /kb cancel to abort.", backupName), true); return SUCCESS; @@ -161,7 +164,9 @@ public final class KBCommands { } // Update pending task - pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName); + //pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName); + File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName)); + pendingOperation = new RestoreOperation(context, backupFile.getAbsolutePath(), getLevelPath(server), backupName); msgWarn(context, String.format("RESET WARNING: You will LOSE YOUR CURRENT WORLD PERMANENTLY! The worlds will be replaced with backup %s . Use /kb confirm to start or /kb cancel to abort.", backupName), true); return SUCCESS; @@ -195,8 +200,14 @@ public final class KBCommands { // Do backup BackupMetadata metadata = new BackupMetadata(System.currentTimeMillis(), backupName); PrintUtil.info("Invoking backup worker ..."); - BackupWorker.invoke(context, backupName, metadata); - return SUCCESS; + //BackupWorker.invoke(context, backupName, metadata); + BackupOperation operation = new BackupOperation(context, backupName, metadata); + if (operation.invoke()) { + return SUCCESS; + } else if (operation.isBlocked()) { + msgWarn(context, "Another task is running, cannot issue new backup at once."); + } + return FAILED; } /** @@ -207,14 +218,14 @@ public final class KBCommands { */ public static int confirm(CommandContext context) { if (pendingOperation == null) { - msgWarn(context, "Nothing to be confirmed. Please execute /kb restore first."); + msgWarn(context, "Nothing to confirm."); return FAILED; } - AbstractConfirmableOperation operation = pendingOperation; + Invokable operation = pendingOperation; pendingOperation = null; - boolean returnValue = operation.confirm(); + boolean returnValue = operation.invoke(); // By the way, update suggestion list. BackupNameSuggestionProvider.updateCandidateList(); diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java index 79105b2..dea79e7 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java +++ b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java @@ -59,7 +59,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback // Print metadata MetadataHolder.setMetadata(metadata); - PrintUtil.info("Recovered from previous backup:"); + PrintUtil.info("Restored from a previous backup:"); PrintUtil.info("Backup Name: " + metadata.getBackupName()); PrintUtil.info("Create Time: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(metadata.getBackupTime()))); diff --git a/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java deleted file mode 100644 index d666a34..0000000 --- a/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.keuin.kbackupfabric.operation; - -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.server.command.ServerCommandSource; - -public abstract class AbstractConfirmableOperation { - - public static AbstractConfirmableOperation createRestoreOperation(CommandContext context, String backupName) { - return new RestoreOperation(context, backupName); - } - - public static AbstractConfirmableOperation createDeleteOperation(CommandContext context, String backupName) { - return new DeleteOperation(context, backupName); - } - - public abstract boolean confirm(); - - @Override - public abstract String toString(); -} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java new file mode 100644 index 0000000..7d3a508 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java @@ -0,0 +1,105 @@ +package com.keuin.kbackupfabric.operation; + +import com.keuin.kbackupfabric.metadata.BackupMetadata; +import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation; +import com.keuin.kbackupfabric.util.PrintUtil; +import com.keuin.kbackupfabric.util.ZipUtil; +import com.keuin.kbackupfabric.util.ZipUtilException; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.world.World; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*; +import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo; + +public class BackupOperation extends InvokableAsyncBlockingOperation { + + private final CommandContext context; + private final String backupName; + private final Map oldWorldsSavingDisabled = new HashMap<>(); + private final BackupMetadata backupMetadata; + private long startTime; + + + public BackupOperation(CommandContext context, String backupName, BackupMetadata backupMetadata) { + super("BackupWorker"); + this.context = context; + this.backupName = backupName; + this.backupMetadata = backupMetadata; + } + + @Override + protected void async() { + String backupSaveDirectory = ""; + MinecraftServer server = context.getSource().getMinecraftServer(); + try { + //// Do our main backup logic + + // Create backup saving directory + File backupSaveDirectoryFile = getBackupSaveDirectory(server); + backupSaveDirectory = backupSaveDirectoryFile.getName(); + if (!backupSaveDirectoryFile.isDirectory() && !backupSaveDirectoryFile.mkdir()) { + msgInfo(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", backupSaveDirectory)); + return; + } + + // Make zip + String levelPath = getLevelPath(server); + String backupFileName = getBackupFileName(backupName); + PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSaveDirectoryFile.toString())); + PrintUtil.info("Compressing level ..."); + ZipUtil.makeBackupZip(levelPath, backupSaveDirectoryFile.toString(), backupFileName, backupMetadata); + File backupZipFile = new File(backupSaveDirectoryFile, backupFileName); + + // Restore old autosave switch stat + server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true)); + + // Print finish message: time elapsed and file size + long timeElapsedMillis = System.currentTimeMillis() - startTime; + String msgText = String.format("Backup finished. Time elapsed: %.2fs.", timeElapsedMillis / 1000.0); + try { + msgText += String.format(" File size: %s.", humanFileSize(backupZipFile.length())); + } catch (SecurityException ignored) { + } + PrintUtil.msgInfo(context, msgText, true); + + } catch (SecurityException e) { + msgInfo(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", backupSaveDirectory)); + } catch (IOException | ZipUtilException e) { + msgInfo(context, "Failed to make zip: " + e.getMessage()); + } + } + + @Override + protected boolean sync() { + //// Save world, save old autosave configs + + PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName)); + + // Get server + MinecraftServer server = context.getSource().getMinecraftServer(); + + // Save old autosave switch stat temporally + oldWorldsSavingDisabled.clear(); + server.getWorlds().forEach(world -> { + oldWorldsSavingDisabled.put(world, world.savingDisabled); + world.savingDisabled = true; + }); + + // Force to save all player data and worlds + PrintUtil.msgInfo(context, "Saving players ..."); + server.getPlayerManager().saveAllPlayerData(); + PrintUtil.msgInfo(context, "Saving worlds ..."); + server.save(true, true, true); + + // Log start time + startTime = System.currentTimeMillis(); + return true; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java index be7734d..ee1a57e 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java @@ -1,5 +1,7 @@ package com.keuin.kbackupfabric.operation; +import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation; +import com.keuin.kbackupfabric.util.BackupNameSuggestionProvider; import com.keuin.kbackupfabric.util.PrintUtil; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.MinecraftServer; @@ -14,19 +16,30 @@ import static com.keuin.kbackupfabric.util.PrintUtil.msgErr; import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo; import static org.apache.commons.io.FileUtils.forceDelete; -class DeleteOperation extends AbstractConfirmableOperation { +public class DeleteOperation extends InvokableAsyncBlockingOperation { //private static final Logger LOGGER = LogManager.getLogger(); private final String backupName; private final CommandContext context; - DeleteOperation(CommandContext context, String backupName) { + public DeleteOperation(CommandContext context, String backupName) { + super("BackupDeletingWorker"); this.backupName = backupName; this.context = context; } @Override - public boolean confirm() { + public String toString() { + return String.format("deletion of %s", backupName); + } + + @Override + protected void async() { + delete(); + BackupNameSuggestionProvider.updateCandidateList(); + } + + private void delete() { MinecraftServer server = context.getSource().getMinecraftServer(); String backupFileName = getBackupFileName(backupName); PrintUtil.info("Deleting backup " + backupName); @@ -37,7 +50,7 @@ class DeleteOperation extends AbstractConfirmableOperation { String msg = "Failed to delete file " + backupFileName; PrintUtil.error(msg); msgErr(context, msg); - return false; + return; } try { if (!backupFile.delete()) @@ -48,11 +61,5 @@ class DeleteOperation extends AbstractConfirmableOperation { } while (backupFile.exists()); PrintUtil.info("Deleted backup " + backupName); msgInfo(context, "Deleted backup " + backupName); - return true; - } - - @Override - public String toString() { - return String.format("deletion of %s", backupName); } } diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java index 4b129d9..cc19627 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java @@ -1,37 +1,49 @@ package com.keuin.kbackupfabric.operation; +import com.keuin.kbackupfabric.operation.abstracts.InvokableBlockingOperation; import com.keuin.kbackupfabric.util.PrintUtil; -import com.keuin.kbackupfabric.worker.RestoreWorker; +import com.keuin.kbackupfabric.util.ZipUtil; +import com.keuin.kbackupfabric.util.ZipUtilException; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import java.io.File; +import java.io.IOException; -import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*; +import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.getBackupFileName; +import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.getBackupSaveDirectory; +import static org.apache.commons.io.FileUtils.forceDelete; -class RestoreOperation extends AbstractConfirmableOperation { +public class RestoreOperation extends InvokableBlockingOperation { //private static final Logger LOGGER = LogManager.getLogger(); private final String backupName; + private final Thread serverThread; + private final String backupFilePath; + private final String levelDirectory; private final CommandContext context; + private final MinecraftServer server; - RestoreOperation(CommandContext context, String backupName) { + public RestoreOperation(CommandContext context, String backupFilePath, String levelDirectory, String backupName) { + server = context.getSource().getMinecraftServer(); this.backupName = backupName; + this.serverThread = server.getThread(); + this.backupFilePath = backupFilePath; + this.levelDirectory = levelDirectory; this.context = context; } @Override - public boolean confirm() { + protected boolean blockingContext() { // do restore to backupName - MinecraftServer server = context.getSource().getMinecraftServer(); PrintUtil.broadcast(String.format("Restoring to previous world %s ...", backupName)); String backupFileName = getBackupFileName(backupName); PrintUtil.debug("Backup file name: " + backupFileName); File backupFile = new File(getBackupSaveDirectory(server), backupFileName); - PrintUtil.msgInfo(context, "Server will shutdown in a few seconds, depended on your world size and the disk speed, the restore progress may take seconds or minutes.", true); + PrintUtil.msgInfo(context, "Server will shutdown in a few seconds, depending on world size and disk speed, the progress may take from seconds to minutes.", true); PrintUtil.msgInfo(context, "Please do not force the server stop, or the level would be broken.", true); PrintUtil.msgInfo(context, "After it shuts down, please restart the server manually.", true); final int WAIT_SECONDS = 10; @@ -42,7 +54,14 @@ class RestoreOperation extends AbstractConfirmableOperation { } } PrintUtil.broadcast("Shutting down ..."); - RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server)); + //RestoreWorker worker = new RestoreWorker(server.getThread(), backupFilePath, levelDirectory); + Thread workerThread = new Thread(new WorkerThread(), "RestoreWorker"); + workerThread.start(); + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + server.stop(false); return true; } @@ -50,4 +69,61 @@ class RestoreOperation extends AbstractConfirmableOperation { public String toString() { return String.format("restoration from %s", backupName); } + + private class WorkerThread implements Runnable { + + @Override + public void run() { + try { + // Wait server thread die + PrintUtil.info("Waiting for the server thread to exit ..."); + while (serverThread.isAlive()) { + try { + serverThread.join(); + } catch (InterruptedException ignored) { + } + } + + PrintUtil.info("Wait for 5 seconds ..."); + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { + } + + // Delete old level + PrintUtil.info("Server stopped. Deleting old level ..."); + File levelDirFile = new File(levelDirectory); + long startTime = System.currentTimeMillis(); + + int failedCounter = 0; + final int MAX_RETRY_TIMES = 20; + while (failedCounter < MAX_RETRY_TIMES) { + System.gc(); + if (!levelDirFile.delete() && levelDirFile.exists()) { + System.gc(); + forceDelete(levelDirFile); // Try to force delete. + } + if (!levelDirFile.exists()) + break; + ++failedCounter; + try { + Thread.sleep(500); + } catch (InterruptedException ignored) { + } + } + if (levelDirFile.exists()) { + PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName())); + return; + } + + // Decompress archive + PrintUtil.info("Decompressing archived level ..."); + ZipUtil.unzip(backupFilePath, levelDirectory, false); + long endTime = System.currentTimeMillis(); + PrintUtil.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0)); + } catch (SecurityException | IOException | ZipUtilException e) { + PrintUtil.error("An exception occurred while restoring: " + e.getMessage()); + } + } + } } diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java new file mode 100644 index 0000000..f1a19de --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java @@ -0,0 +1,53 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +public abstract class AbstractAsyncOperation extends AbstractSerializedOperation { + + private final Thread thread; + private final String name; + private final Object sync = new Object(); + + protected AbstractAsyncOperation(String name) { + this.name = name; + this.thread = new Thread(this::async, name); + } + + /** + * Start the worker thread. + * + * @return true if succeed starting, false if already started. + */ + @Override + protected final boolean operate() { + synchronized (sync) { + if (thread.isAlive()) + return false; + if (!sync()) + return false; + thread.start(); + return true; + } + } + + /** + * Implement your async operation here. + * When this method returns, the operation must finish. + */ + protected abstract void async(); + + /** + * If necessary, implement your sync operations here. + * It will be invoked before starting the async thread. + */ + protected boolean sync() { + return true; + } + + public final String getName() { + return name; + } + + @Override + public String toString() { + return "operation " + name; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java new file mode 100644 index 0000000..de672cd --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java @@ -0,0 +1,58 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +import com.keuin.kbackupfabric.operation.abstracts.i.Blocking; + +public abstract class AbstractBlockingOperation extends AbstractSerializedOperation implements Blocking { + + private static final Object sync = new Object(); + private static boolean isBlocking = false; + private boolean wasBlocked = false; + private boolean noUnblocking = false; + + @Override + protected final boolean operate() { + synchronized (sync) { + if (isBlocking) { + System.out.println("blocked."); + wasBlocked = true; + return false; + } else { + System.out.println("not blocked."); + wasBlocked = false; + isBlocking = true; + } + } + boolean exitCode = blockingContext(); + if (!noUnblocking) + isBlocking = false; + return exitCode; + } + + public final boolean isBlocked() { + return wasBlocked; + } + + protected final void block(boolean blockState) { + isBlocking = blockState; + } + + protected void noUnblocking(boolean b) { + noUnblocking = b; + } + + protected boolean blockAndGet() { + synchronized (sync) { + boolean b = isBlocking; + isBlocking = true; + return b; + } + } + + /** + * Implement your blocked operation here. + * + * @return the stat code. + */ + protected abstract boolean blockingContext(); + +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java new file mode 100644 index 0000000..0a56008 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java @@ -0,0 +1,11 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +public abstract class AbstractConfirmableOperation extends AbstractSerializedOperation { + + public final boolean confirm() { + return operate(); + } + + @Override + public abstract String toString(); +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java new file mode 100644 index 0000000..3168e8c --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java @@ -0,0 +1,12 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +public abstract class AbstractSerializedOperation { + /** + * Do your operation here. + * This method is not designed to be public. + * When this method returns, the operation must have finished. + * + * @return the stat code. + */ + protected abstract boolean operate(); +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java new file mode 100644 index 0000000..6268d5a --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java @@ -0,0 +1,66 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +import com.keuin.kbackupfabric.operation.abstracts.i.Blocking; +import com.keuin.kbackupfabric.operation.abstracts.i.Invokable; + +public abstract class InvokableAsyncBlockingOperation implements Invokable, Blocking { + + private final InvokableAsyncOperation asyncOperation; + private final HackedBlockingOperation blockingOperation; + + public InvokableAsyncBlockingOperation(String name) { + asyncOperation = new InvokableAsyncOperation(name) { + @Override + protected void async() { + InvokableAsyncBlockingOperation.this.async(); + // When the async operation finishes, unblock + blockingOperation.noUnblocking(false); + blockingOperation.block(false); + } + + @Override + protected boolean sync() { + return InvokableAsyncBlockingOperation.this.sync(); + } + }; + + blockingOperation = new HackedBlockingOperation(); + } + + @Override + public boolean invoke() { + return blockingOperation.invoke(); + } + + @Override + public boolean isBlocked() { + return blockingOperation.isBlocked(); + } + + protected abstract void async(); + + protected boolean sync() { + return true; + } + + private class HackedBlockingOperation extends InvokableBlockingOperation { + + @Override + protected boolean blockingContext() { + + noUnblocking(true); + return asyncOperation.invoke(); + } + + @Override + public void noUnblocking(boolean b) { + super.noUnblocking(b); + } + + @Override + public void block(boolean blockState) { + super.block(blockState); + } + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java new file mode 100644 index 0000000..518f670 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java @@ -0,0 +1,32 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +public abstract class InvokableAsyncOperation extends InvokableOperation { + + private final AbstractAsyncOperation asyncOperation; + + + public InvokableAsyncOperation(String name) { + asyncOperation = new AbstractAsyncOperation(name) { + @Override + protected void async() { + InvokableAsyncOperation.this.async(); + } + + @Override + protected boolean sync() { + return InvokableAsyncOperation.this.sync(); + } + }; + } + + protected abstract void async(); + + protected boolean sync() { + return true; + } + + @Override + protected boolean operate() { + return asyncOperation.operate(); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java new file mode 100644 index 0000000..b1a3748 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java @@ -0,0 +1,39 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +import com.keuin.kbackupfabric.operation.abstracts.i.Blocking; + +public abstract class InvokableBlockingOperation extends InvokableOperation implements Blocking { + + private final AbstractBlockingOperation operation = new AbstractBlockingOperation() { + @Override + protected boolean blockingContext() { + return InvokableBlockingOperation.this.blockingContext(); + } + }; + + @Override + protected final boolean operate() { + return operation.operate(); + } + + /** + * Implement your blocked operation here. + * + * @return stat code. + */ + protected abstract boolean blockingContext(); + + protected void block(boolean blockState) { + operation.block(blockState); + } + + protected void noUnblocking(boolean b) { + operation.noUnblocking(b); + } + + @Override + public final boolean isBlocked() { + return operation.isBlocked(); + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java new file mode 100644 index 0000000..47c1ca8 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java @@ -0,0 +1,9 @@ +package com.keuin.kbackupfabric.operation.abstracts; + +import com.keuin.kbackupfabric.operation.abstracts.i.Invokable; + +public abstract class InvokableOperation extends AbstractSerializedOperation implements Invokable { + public boolean invoke() { + return operate(); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java new file mode 100644 index 0000000..ba1b003 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java @@ -0,0 +1,5 @@ +package com.keuin.kbackupfabric.operation.abstracts.i; + +public interface Blocking { + boolean isBlocked(); +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java new file mode 100644 index 0000000..74051e3 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java @@ -0,0 +1,5 @@ +package com.keuin.kbackupfabric.operation.abstracts.i; + +public interface Invokable { + boolean invoke(); +} diff --git a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java deleted file mode 100644 index d6db636..0000000 --- a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.keuin.kbackupfabric.worker; - -import com.keuin.kbackupfabric.metadata.BackupMetadata; -import com.keuin.kbackupfabric.util.PrintUtil; -import com.keuin.kbackupfabric.util.ZipUtil; -import com.keuin.kbackupfabric.util.ZipUtilException; -import com.mojang.brigadier.context.CommandContext; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.world.World; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*; -import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo; - -/** - * The backup worker - * To invoke this worker, simply call invoke() method. - */ -public final class BackupWorker implements Runnable { - - //private static final Logger LOGGER = LogManager.getLogger(); - private final CommandContext context; - private final String backupName; - private final Map oldWorldsSavingDisabled; - private final BackupMetadata backupMetadata; - private final long startTime; - - private BackupWorker(CommandContext context, String backupName, Map oldWorldsSavingDisabled, BackupMetadata backupMetadata, long startTime) { - this.context = context; - this.backupName = backupName; - this.oldWorldsSavingDisabled = oldWorldsSavingDisabled; - this.backupMetadata = backupMetadata; - this.startTime = startTime; - } - - public static void invoke(CommandContext context, String backupName, BackupMetadata backupMetadata) { - //// Save world, save old autosave configs - - PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName)); - Map oldWorldsSavingDisabled = new HashMap<>(); // old switch stat - - // Get server - MinecraftServer server = context.getSource().getMinecraftServer(); - - // Save old autosave switch stat temporally - server.getWorlds().forEach(world -> { - oldWorldsSavingDisabled.put(world, world.savingDisabled); - world.savingDisabled = true; - }); - - // Force to save all player data and worlds - PrintUtil.msgInfo(context, "Saving players ..."); - server.getPlayerManager().saveAllPlayerData(); - PrintUtil.msgInfo(context, "Saving worlds ..."); - server.save(true, true, true); - - // Start threaded worker - BackupWorker worker = new BackupWorker(context, backupName, oldWorldsSavingDisabled, backupMetadata, System.currentTimeMillis()); - Thread workerThread = new Thread(worker, "BackupWorker"); - workerThread.start(); - } - - @Override - public void run() { - String backupSaveDirectory = ""; - MinecraftServer server = context.getSource().getMinecraftServer(); - try { - //// Do our main backup logic - - // Create backup saving directory - File backupSaveDirectoryFile = getBackupSaveDirectory(server); - backupSaveDirectory = backupSaveDirectoryFile.getName(); - if (!backupSaveDirectoryFile.isDirectory() && !backupSaveDirectoryFile.mkdir()) { - msgInfo(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", backupSaveDirectory)); - return; - } - - // Make zip - String levelPath = getLevelPath(server); - String backupFileName = getBackupFileName(backupName); - PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSaveDirectoryFile.toString())); - PrintUtil.info("Compressing level ..."); - ZipUtil.makeBackupZip(levelPath, backupSaveDirectoryFile.toString(), backupFileName, backupMetadata); - File backupZipFile = new File(backupSaveDirectoryFile, backupFileName); - - // Restore old autosave switch stat - server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true)); - - // Print finish message: time elapsed and file size - long timeElapsedMillis = System.currentTimeMillis() - startTime; - String msgText = String.format("Backup finished. Time elapsed: %.2fs.", timeElapsedMillis / 1000.0); - try { - msgText += String.format(" File size: %s.", humanFileSize(backupZipFile.length())); - } catch (SecurityException ignored) { - } - PrintUtil.msgInfo(context, msgText, true); - - } catch (SecurityException e) { - msgInfo(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", backupSaveDirectory)); - } catch (IOException | ZipUtilException e) { - msgInfo(context, "Failed to make zip: " + e.getMessage()); - } - } -} diff --git a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java deleted file mode 100644 index 58560a1..0000000 --- a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.keuin.kbackupfabric.worker; - -import com.keuin.kbackupfabric.util.PrintUtil; -import com.keuin.kbackupfabric.util.ZipUtil; -import com.keuin.kbackupfabric.util.ZipUtilException; -import net.minecraft.server.MinecraftServer; - -import java.io.File; -import java.io.IOException; - -import static org.apache.commons.io.FileUtils.forceDelete; - -/** - * The restore worker - * To invoke this worker, simply call invoke() method. - */ -public final class RestoreWorker implements Runnable { - - //private static final Logger LOGGER = LogManager.getLogger(); - - private final Thread serverThread; - private final String backupFilePath; - private final String levelDirectory; - - private RestoreWorker(Thread serverThread, String backupFilePath, String levelDirectory) { - this.serverThread = serverThread; - this.backupFilePath = backupFilePath; - this.levelDirectory = levelDirectory; - } - - public static void invoke(MinecraftServer server, String backupFilePath, String levelDirectory) { - RestoreWorker worker = new RestoreWorker(server.getThread(), backupFilePath, levelDirectory); - Thread workerThread = new Thread(worker, "RestoreWorker"); - workerThread.start(); - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - } - server.stop(false); - } - - @Override - public void run() { - try { - // Wait server thread die - PrintUtil.info("Waiting for the server thread to exit ..."); - while (serverThread.isAlive()) { - try { - serverThread.join(); - } catch (InterruptedException ignored) { - } - } - - PrintUtil.info("Wait for 5 seconds ..."); - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { - } - - // Delete old level - PrintUtil.info("Server stopped. Deleting old level ..."); - File levelDirFile = new File(levelDirectory); - long startTime = System.currentTimeMillis(); - - int failedCounter = 0; - final int MAX_RETRY_TIMES = 20; - while (failedCounter < MAX_RETRY_TIMES) { - System.gc(); - if (!levelDirFile.delete() && levelDirFile.exists()) { - System.gc(); - forceDelete(levelDirFile); // Try to force delete. - } - if (!levelDirFile.exists()) - break; - ++failedCounter; - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - } - } - if (levelDirFile.exists()) { - PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName())); - return; - } - - // Decompress archive - PrintUtil.info("Decompressing archived level"); - ZipUtil.unzip(backupFilePath, levelDirectory, false); - long endTime = System.currentTimeMillis(); - PrintUtil.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0)); - } catch (SecurityException | IOException | ZipUtilException e) { - PrintUtil.error("An exception occurred while restoring: " + e.getMessage()); - } - } -} -- cgit v1.2.3