From f056dcdbc9d5edcde2a0c4ff3eb0072d4ae02e27 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Wed, 6 Sep 2023 00:13:53 +0800 Subject: [PATCH] Better error handling of world folder deletion --- .../MultiverseCore/utils/MVCorei18n.java | 1 + .../MultiverseCore/worldnew/WorldManager.java | 45 ++++++++++++++----- .../worldnew/helpers/FilesManipulator.java | 31 +++++++++++++ .../worldnew/results/DeleteWorldResult.java | 1 + .../resources/multiverse-core_en.properties | 3 +- 5 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/onarandombox/MultiverseCore/worldnew/helpers/FilesManipulator.java diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java b/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java index 8e762478..189f596e 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/MVCorei18n.java @@ -101,6 +101,7 @@ public enum MVCorei18n implements MessageKeyProvider { DELETEWORLD_DELETED, DELETEWORLD_WORLDNONEXISTENT, + DELETEWORLD_LOADFAILED, DELETEWORLD_WORLDFOLDERNOTFOUND, DELETEWORLD_FAILEDTODELETEFOLDER, diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java index c208facc..147cda2b 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/WorldManager.java @@ -10,6 +10,7 @@ import com.onarandombox.MultiverseCore.utils.result.Result; import com.onarandombox.MultiverseCore.worldnew.config.WorldConfig; import com.onarandombox.MultiverseCore.worldnew.config.WorldsConfigManager; import com.onarandombox.MultiverseCore.worldnew.generators.GeneratorProvider; +import com.onarandombox.MultiverseCore.worldnew.helpers.FilesManipulator; import com.onarandombox.MultiverseCore.worldnew.options.CreateWorldOptions; import com.onarandombox.MultiverseCore.worldnew.options.ImportWorldOptions; import com.onarandombox.MultiverseCore.worldnew.results.CreateWorldResult; @@ -52,6 +53,7 @@ public class WorldManager { private final WorldsConfigManager worldsConfigManager; private final WorldNameChecker worldNameChecker; private final GeneratorProvider generatorProvider; + private final FilesManipulator filesManipulator; private final BlockSafety blockSafety; private final SafeTTeleporter safeTTeleporter; private final LocationManipulation locationManipulation; @@ -61,6 +63,7 @@ public class WorldManager { @NotNull WorldsConfigManager worldsConfigManager, @NotNull WorldNameChecker worldNameChecker, @NotNull GeneratorProvider generatorProvider, + @NotNull FilesManipulator filesManipulator, @NotNull BlockSafety blockSafety, @NotNull SafeTTeleporter safeTTeleporter, @NotNull LocationManipulation locationManipulation @@ -73,6 +76,7 @@ public class WorldManager { this.worldsConfigManager = worldsConfigManager; this.worldNameChecker = worldNameChecker; this.generatorProvider = generatorProvider; + this.filesManipulator = filesManipulator; this.blockSafety = blockSafety; this.safeTTeleporter = safeTTeleporter; this.locationManipulation = locationManipulation; @@ -396,18 +400,38 @@ public class WorldManager { } /** - * Deletes an existing multiverse world entirely. Warning: This will delete all world files. + * Deletes an existing multiverse world entirely. World will be loaded if it is not already loaded. + * Warning: This will delete all world files. * * @param worldName The name of the world to delete. * @return The result of the delete action. */ public Result deleteWorld(@NotNull String worldName) { - // TODO: Attempt to load offline world - return getMVWorld(worldName) + return getOfflineWorld(worldName) .map(this::deleteWorld) .getOrElse(() -> Result.failure(DeleteWorldResult.Failure.WORLD_NON_EXISTENT, replace("{world}").with(worldName))); } + /** + * Deletes an existing multiverse world entirely. World will be loaded if it is not already loaded. + * Warning: This will delete all world files. + * + * @param world The offline world to delete. + * @return The result of the delete action. + */ + public Result deleteWorld(@NotNull OfflineWorld world) { + return getMVWorld(world).fold( + () -> { + var result = loadWorld(world); + if (result.isFailure()) { + return Result.failure(DeleteWorldResult.Failure.LOAD_FAILED, replace("{world}").with(world.getName())); + } + return deleteWorld(world); + }, + this::deleteWorld + ); + } + /** * Deletes an existing multiverse world entirely. Warning: This will delete all world files. * @@ -427,13 +451,14 @@ public class WorldManager { } // Erase world files from disk - // TODO: Config options to keep certain files - if (!FileUtils.deleteFolder(worldFolder)) { - Logging.severe("Failed to delete world folder: " + worldFolder); - return Result.failure(DeleteWorldResult.Failure.FAILED_TO_DELETE_FOLDER, replace("{world}").with(world.getName())); - } - - return Result.success(DeleteWorldResult.Success.DELETED, replace("{world}").with(world.getName())); + // TODO: Possible config options to keep certain files + return filesManipulator.deleteFolder(worldFolder).fold( + (exception) -> Result.failure(DeleteWorldResult.Failure.FAILED_TO_DELETE_FOLDER, + replace("{world}").with(world.getName()), + replace("{error}").with(exception.getMessage()) + ), + (success) -> Result.success(DeleteWorldResult.Success.DELETED, replace("{world}").with(world.getName())) + ); } /** diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/helpers/FilesManipulator.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/helpers/FilesManipulator.java new file mode 100644 index 00000000..d499114c --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/helpers/FilesManipulator.java @@ -0,0 +1,31 @@ +package com.onarandombox.MultiverseCore.worldnew.helpers; + +import com.dumptruckman.minecraft.util.Logging; +import io.vavr.control.Try; +import org.jvnet.hk2.annotations.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.stream.Stream; + +@Service +public class FilesManipulator { + + public Try deleteFolder(File file) { + return deleteFolder(file.toPath()); + } + + public Try deleteFolder(Path path) { + try (Stream files = Files.walk(path)) { + files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + return Try.success(null); + } catch (IOException e) { + Logging.severe("Failed to delete folder: " + path.toAbsolutePath()); + e.printStackTrace(); + return Try.failure(e); + } + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/worldnew/results/DeleteWorldResult.java b/src/main/java/com/onarandombox/MultiverseCore/worldnew/results/DeleteWorldResult.java index c6afe995..d196cd7e 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/worldnew/results/DeleteWorldResult.java +++ b/src/main/java/com/onarandombox/MultiverseCore/worldnew/results/DeleteWorldResult.java @@ -25,6 +25,7 @@ public class DeleteWorldResult { public enum Failure implements FailureReason { WORLD_NON_EXISTENT(MVCorei18n.DELETEWORLD_WORLDNONEXISTENT), + LOAD_FAILED(MVCorei18n.DELETEWORLD_LOADFAILED), WORLD_FOLDER_NOT_FOUND(MVCorei18n.DELETEWORLD_WORLDFOLDERNOTFOUND), REMOVE_FAILED(null), FAILED_TO_DELETE_FOLDER(MVCorei18n.DELETEWORLD_FAILEDTODELETEFOLDER), diff --git a/src/main/resources/multiverse-core_en.properties b/src/main/resources/multiverse-core_en.properties index ac38045e..9b43fa39 100644 --- a/src/main/resources/multiverse-core_en.properties +++ b/src/main/resources/multiverse-core_en.properties @@ -137,8 +137,9 @@ mv-core.createworld.bukkitcreationfailed=Bukkit failed to create world '{world}' mv-core.deleteworld.deleted=&aWorld '{world}' deleted! mv-core.deleteworld.worldnonexistent=World '{world}' not found! +mv-core.deleteworld.loadfailed=Unable to load world '{world}', does the world folder exist?&f You can run '&a/mv remove {world}&f' to remove it from Multiverse, or attempt to load again with '&a/mv load {world}&f'. mv-core.deleteworld.worldfoldernotfound=World '{world}' folder not found! -mv-core.deleteworld.failedtodeletefolder=Failed to delete world '{world}' folder!& fSee console for more details. +mv-core.deleteworld.failedtodeletefolder=Failed to delete world folder '{world}': {error}\n&fSee console for more details. mv-core.importworld.imported=&aWorld '{world}' imported! mv-core.importworld.invalidworldname=World '{world}' contains invalid characters!