diff options
18 files changed, 450 insertions, 185 deletions
diff --git a/gradle.properties b/gradle.properties index 77a15c7..7bd2d48 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.14.4 yarn_mappings=1.14.4+build.16 loader_version=0.8.2+build.194 # Mod Properties -mod_version=1.3.1-dev +mod_version=1.3.2-dev maven_group=com.keuin.kbackupfabric archives_base_name=kbackup-fabric # Dependencies 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<String> 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<ServerCommandSource> context) { if (pendingOperation == null) { - msgWarn(context, "Nothing to be confirmed. Please execute /kb restore <backup_name> 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<ServerCommandSource> context, String backupName) { - return new RestoreOperation(context, backupName); - } - - public static AbstractConfirmableOperation createDeleteOperation(CommandContext<ServerCommandSource> 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/worker/BackupWorker.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java index d6db636..7d3a508 100644 --- a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java @@ -1,6 +1,7 @@ -package com.keuin.kbackupfabric.worker; +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; @@ -17,56 +18,24 @@ 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 { +public class BackupOperation extends InvokableAsyncBlockingOperation { - //private static final Logger LOGGER = LogManager.getLogger(); private final CommandContext<ServerCommandSource> context; private final String backupName; - private final Map<World, Boolean> oldWorldsSavingDisabled; + private final Map<World, Boolean> oldWorldsSavingDisabled = new HashMap<>(); private final BackupMetadata backupMetadata; - private final long startTime; + private long startTime; - private BackupWorker(CommandContext<ServerCommandSource> context, String backupName, Map<World, Boolean> oldWorldsSavingDisabled, BackupMetadata backupMetadata, long startTime) { + + public BackupOperation(CommandContext<ServerCommandSource> context, String backupName, BackupMetadata backupMetadata) { + super("BackupWorker"); this.context = context; this.backupName = backupName; - this.oldWorldsSavingDisabled = oldWorldsSavingDisabled; this.backupMetadata = backupMetadata; - this.startTime = startTime; - } - - public static void invoke(CommandContext<ServerCommandSource> context, String backupName, BackupMetadata backupMetadata) { - //// Save world, save old autosave configs - - PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName)); - Map<World, Boolean> 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() { + protected void async() { String backupSaveDirectory = ""; MinecraftServer server = context.getSource().getMinecraftServer(); try { @@ -106,4 +75,31 @@ public final class BackupWorker implements Runnable { 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<ServerCommandSource> context; - DeleteOperation(CommandContext<ServerCommandSource> context, String backupName) { + public DeleteOperation(CommandContext<ServerCommandSource> 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<ServerCommandSource> context; + private final MinecraftServer server; - RestoreOperation(CommandContext<ServerCommandSource> context, String backupName) { + public RestoreOperation(CommandContext<ServerCommandSource> 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/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()); - } - } -} |