Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.helpers.PlayerWorldTeleporter;
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;

@Service
class DeleteCommand extends CoreCommand {
Expand Down Expand Up @@ -94,7 +95,7 @@ private void runDeleteCommand(MVCommandIssuer issuer, MultiverseWorld world, Par
}

private void doWorldDeleting(MVCommandIssuer issuer, MultiverseWorld world) {
worldManager.deleteWorld(world)
worldManager.deleteWorld(DeleteWorldOptions.world(world))
.onSuccess(deletedWorldName -> {
Logging.fine("World delete success: " + deletedWorldName);
issuer.sendInfo(MVCorei18n.DELETE_SUCCESS, Replace.WORLD.with(deletedWorldName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import org.mvplugins.multiverse.core.command.LegacyAliasCommand;
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
import org.mvplugins.multiverse.core.command.MVCommandManager;
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
import org.mvplugins.multiverse.core.command.flag.CommandValueFlag;
Expand All @@ -30,6 +29,7 @@
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.utils.REPatterns;
import org.mvplugins.multiverse.core.utils.WorldTickDeferrer;
import org.mvplugins.multiverse.core.utils.result.AsyncAttemptsAggregate;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
Expand Down Expand Up @@ -112,7 +112,8 @@ private void doWorldRegening(
.seed(parsedFlags.flagValue(flags.seed))
.keepWorldConfig(!parsedFlags.hasFlag(flags.resetWorldConfig))
.keepGameRule(!parsedFlags.hasFlag(flags.resetGamerules))
.keepWorldBorder(!parsedFlags.hasFlag(flags.resetWorldBorder));
.keepWorldBorder(!parsedFlags.hasFlag(flags.resetWorldBorder))
.keepFiles(parsedFlags.flagValue(flags.keepFiles));

worldManager.regenWorld(regenWorldOptions).onSuccess(newWorld -> {
Logging.fine("World regen success: " + newWorld);
Expand Down Expand Up @@ -153,6 +154,13 @@ private Flags(@NotNull CommandFlagsManager flagsManager) {
private final CommandFlag resetWorldBorder = flag(CommandFlag.builder("--reset-world-border")
.addAlias("-wb")
.build());

private final CommandValueFlag<List> keepFiles = flag(CommandValueFlag.builder("--keep-files", List.class)
.addAlias("-f")
.completion(input -> List.of("paper-world.yml"))
.defaultValue(Collections.emptyList())
.context(input -> List.of(REPatterns.COMMA.split(input)))
.build());
}

@Service
Expand Down
37 changes: 31 additions & 6 deletions src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
Expand All @@ -20,7 +23,6 @@
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
Expand Down Expand Up @@ -102,7 +104,11 @@ public File getServerFolder() {
* the folder could not be deleted.
*/
public Try<Void> deleteFolder(File file) {
return deleteFolder(file.toPath());
return deleteFolder(file, Collections.emptyList());
}

public Try<Void> deleteFolder(File file, Collection<String> keepFiles) {
return deleteFolder(file.toPath(), keepFiles);
}

/**
Expand All @@ -112,11 +118,19 @@ public Try<Void> deleteFolder(File file) {
* @return A {@link Try} that will contain {@code null} if the folder was deleted successfully, or an exception if
* the folder could not be deleted.
*/
public Try<Void> deleteFolder(Path path) {
public Try<Void> deleteFolder(Path path, Collection<String> keepFiles) {
try (Stream<Path> files = Files.walk(path)) {
files.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(file -> {
if (!isDirectoryEmpty(file)) {
Logging.finest("Cannot delete folder as it is not empty: " + file);
return;
}
if (file.isFile() && keepFiles.contains(file.getName())) {
Logging.finest("Keeping file: " + file);
return;
}
if (!file.delete()) {
Logging.warning("Failed to delete file: " + file);
throw new IllegalStateException("Failed to delete file: " + file);
}
});
return Try.success(null);
Expand All @@ -127,6 +141,17 @@ public Try<Void> deleteFolder(Path path) {
}
}

private boolean isDirectoryEmpty(File file) {
if (!file.isDirectory()) {
return true;
}
try (Stream<Path> entries = Files.list(file.toPath())) {
return entries.findFirst().isEmpty();
} catch (IOException e) {
return true;
}
}

/**
* Copies all the content of the given folder to the given target folder.
*
Expand Down
37 changes: 13 additions & 24 deletions src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.mvplugins.multiverse.core.world.helpers.WorldNameChecker;
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions;
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions;
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions;
import org.mvplugins.multiverse.core.world.options.ImportWorldOptions;
import org.mvplugins.multiverse.core.world.options.KeepWorldSettingsOptions;
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions;
Expand Down Expand Up @@ -211,7 +212,7 @@ private Attempt<CreateWorldOptions, CreateFailureReason> validateCreateWorldOpti
return worldActionResult(CreateFailureReason.WORLD_EXIST_LOADED, options.worldName());
} else if (getWorld(options.worldName()).isDefined()) {
return worldActionResult(CreateFailureReason.WORLD_EXIST_UNLOADED, options.worldName());
} else if (worldNameChecker.hasWorldFolder(options.worldName())) {
} else if (options.doFolderCheck() && worldNameChecker.hasWorldFolder(options.worldName())) {
return worldActionResult(CreateFailureReason.WORLD_EXIST_FOLDER, options.worldName());
}
return worldActionResult(options);
Expand Down Expand Up @@ -506,28 +507,15 @@ private Attempt<String, RemoveFailureReason> removeWorldFromConfig(@NotNull Mult
* 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.
* @param options The options for customizing the deletion of a world.
* @return The result of the delete action.
*/
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull String worldName) {
return getWorld(worldName)
.map(this::deleteWorld)
.getOrElse(() -> worldActionResult(DeleteFailureReason.WORLD_NON_EXISTENT, 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 world to delete.
* @return The result of the delete action.
*/
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull MultiverseWorld world) {
return getLoadedWorld(world).fold(
() -> loadWorld(world)
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull DeleteWorldOptions options) {
return getLoadedWorld(options.world()).fold(
() -> loadWorld(options.world())
.transform(DeleteFailureReason.LOAD_FAILED)
.mapAttempt(this::deleteWorld),
this::deleteWorld);
.mapAttempt(world -> doDeleteWorld(world, options)),
world -> doDeleteWorld(world, options));
}

/**
Expand All @@ -536,7 +524,7 @@ public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull MultiverseWorld
* @param world The multiverse world to delete.
* @return The result of the delete action.
*/
public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull LoadedMultiverseWorld world) {
private Attempt<String, DeleteFailureReason> doDeleteWorld(@NotNull LoadedMultiverseWorld world, @NotNull DeleteWorldOptions options) {
AtomicReference<File> worldFolder = new AtomicReference<>();
return validateWorldToDelete(world)
.peek(worldFolder::set)
Expand All @@ -548,7 +536,7 @@ public Attempt<String, DeleteFailureReason> deleteWorld(@NotNull LoadedMultivers
: Attempt.success(null);
})
.mapAttempt(() -> removeWorld(world).transform(DeleteFailureReason.REMOVE_FAILED))
.mapAttempt(() -> fileUtils.deleteFolder(worldFolder.get()).fold(
.mapAttempt(() -> fileUtils.deleteFolder(worldFolder.get(), options.keepFiles()).fold(
exception -> worldActionResult(DeleteFailureReason.FAILED_TO_DELETE_FOLDER,
world.getName(), exception),
success -> worldActionResult(world.getName())));
Expand Down Expand Up @@ -665,9 +653,10 @@ public Attempt<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull Re
.generator(world.getGenerator())
.seed(options.seed())
.useSpawnAdjust(!shouldKeepSpawnLocation && world.getAdjustSpawn())
.worldType(world.getWorldType().getOrElse(WorldType.NORMAL));
.worldType(world.getWorldType().getOrElse(WorldType.NORMAL))
.doFolderCheck(options.keepFiles().isEmpty());

return deleteWorld(world)
return deleteWorld(DeleteWorldOptions.world(world).keepFiles(options.keepFiles()))
.transform(RegenFailureReason.DELETE_FAILED)
.mapAttempt(() -> createWorld(createWorldOptions).transform(RegenFailureReason.CREATE_FAILED))
.onSuccess(newWorld -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public final class CreateWorldOptions {
private long seed;
private boolean useSpawnAdjust = true;
private WorldType worldType = WorldType.NORMAL;
private boolean doFolderCheck = true;

/**
* Creates a new {@link CreateWorldOptions} instance with the given world name.
Expand Down Expand Up @@ -239,4 +240,24 @@ public boolean useSpawnAdjust() {
public @NotNull WorldType worldType() {
return worldType;
}

/**
* Sets whether to ensure folder does not exist before creating the world.
*
* @param doFolderCheckInput Whether to do the folder check
* @return This {@link CreateWorldOptions} instance
*/
public @NotNull CreateWorldOptions doFolderCheck(boolean doFolderCheckInput) {
this.doFolderCheck = doFolderCheckInput;
return this;
}

/**
* Gets whether to ensure folder does not exist before creating the world.
*
* @return Whether to do the folder check
*/
public boolean doFolderCheck() {
return doFolderCheck;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.mvplugins.multiverse.core.world.options;

import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.world.MultiverseWorld;

import java.util.Collections;
import java.util.List;

/**
* Options for customizing the deletion of a world.
*/
public final class DeleteWorldOptions {

/**
* Creates a new {@link DeleteWorldOptions} instance with the given world.
*
* @param world The world to delete.
* @return A new {@link DeleteWorldOptions} instance.
*/
public static @NotNull DeleteWorldOptions world(@NotNull MultiverseWorld world) {
return new DeleteWorldOptions(world);
}

private final MultiverseWorld world;
private List<String> keepFiles = Collections.emptyList();

DeleteWorldOptions(MultiverseWorld world) {
this.world = world;
}

public MultiverseWorld world() {
return world;
}

/**
* Sets the files to keep during deletion.
*
* @param keepFilesInput The files to keep during deletion.
* @return This {@link DeleteWorldOptions} instance.
*/
public @NotNull DeleteWorldOptions keepFiles(List<String> keepFilesInput) {
this.keepFiles = keepFilesInput == null ? Collections.emptyList() : keepFilesInput.stream().toList();
return this;
}

/**
* Gets the files to keep during deletion. Note: The list is unmodifiable.
*
* @return The files to keep during deletion.
*/
public List<String> keepFiles() {
return keepFiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;

import java.util.Collections;
import java.util.List;

/**
* Options for customizing the regeneration of a world.
*/
Expand All @@ -31,6 +34,7 @@ public final class RegenWorldOptions implements KeepWorldSettingsOptions {
private boolean keepWorldBorder = true;
private boolean randomSeed = false;
private long seed = UNINITIALIZED_SEED_VALUE;
private List<String> keepFiles = Collections.emptyList();

RegenWorldOptions(@NotNull LoadedMultiverseWorld world) {
this.world = world;
Expand Down Expand Up @@ -211,4 +215,24 @@ public long seed() {
}
return world.getSeed();
}

/**
* Sets the files to keep during regeneration.
*
* @param keepFilesInput The files to keep during regeneration.
* @return This {@link RegenWorldOptions} instance.
*/
public @NotNull RegenWorldOptions keepFiles(@Nullable List<String> keepFilesInput) {
this.keepFiles = keepFilesInput == null ? Collections.emptyList() : keepFilesInput.stream().toList();
return this;
}

/**
* Gets the files to keep during regeneration. Note: The list is unmodifiable.
*
* @return The files to keep during regeneration.
*/
public @NotNull List<String> keepFiles() {
return keepFiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.core.TestWithMockBukkit
import org.mvplugins.multiverse.core.event.world.*
import org.mvplugins.multiverse.core.world.options.CloneWorldOptions
import org.mvplugins.multiverse.core.world.options.CreateWorldOptions
import org.mvplugins.multiverse.core.world.options.DeleteWorldOptions
import org.mvplugins.multiverse.core.world.options.RegenWorldOptions
import org.mvplugins.multiverse.core.world.options.UnloadWorldOptions
import org.mvplugins.multiverse.core.world.reasons.CloneFailureReason
Expand Down Expand Up @@ -112,8 +113,10 @@ class WorldManagerTest : TestWithMockBukkit() {

@Test
fun `Delete world`() {
assertTrue(worldManager.deleteWorld(world).isSuccess)
assertTrue(File(Bukkit.getWorldContainer(), "world").isDirectory)
assertTrue(worldManager.deleteWorld(DeleteWorldOptions.world(world)).isSuccess)
assertFalse(worldManager.getLoadedWorld("world").isDefined)
assertFalse(File(Bukkit.getWorldContainer(), "world").isDirectory)

assertThat(server.pluginManager, hasFiredEventInstance(MVWorldDeleteEvent::class.java))
assertThat(server.pluginManager, hasFiredEventInstance(MVWorldUnloadedEvent::class.java))
Expand Down