Skip to content

Add option to keep files during world regen #3213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 3, 2025
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 @@
* 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) {

Check warning on line 110 in src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Missing a Javadoc comment. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java:110:5: warning: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
return deleteFolder(file.toPath(), keepFiles);
}

/**
Expand All @@ -112,11 +118,19 @@
* @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) {

Check warning on line 121 in src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Expected @param tag for 'keepFiles'. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java:121:65: warning: Expected @param tag for 'keepFiles'. (com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck)
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 @@
}
}

private boolean isDirectoryEmpty(File file) {

Check warning on line 144 in src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Return count is 3 (max allowed for non-void methods/lambdas is 2). Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/utils/FileUtils.java:144:5: info: Return count is 3 (max allowed for non-void methods/lambdas is 2). (com.puppycrawl.tools.checkstyle.checks.coding.ReturnCountCheck)
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 @@
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 @@
* 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 @@
* @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) {

Check warning on line 527 in src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Line is longer than 120 characters (found 139). Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java:527:0: warning: Line is longer than 120 characters (found 139). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
AtomicReference<File> worldFolder = new AtomicReference<>();
return validateWorldToDelete(world)
.peek(worldFolder::set)
Expand All @@ -548,7 +536,7 @@
: 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 @@
.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;

Check warning on line 4 in src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 'org.mvplugins.multiverse.core.world.MultiverseWorld' should be separated from previous imports. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java:4:1: warning: 'org.mvplugins.multiverse.core.world.MultiverseWorld' should be separated from previous imports. (com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck)

import java.util.Collections;

Check warning on line 6 in src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Wrong order for 'java.util.Collections' import. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java:6:1: warning: Wrong order for 'java.util.Collections' import. (com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck)
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() {

Check warning on line 31 in src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Missing a Javadoc comment. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/world/options/DeleteWorldOptions.java:31:5: warning: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
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;

Check warning on line 10 in src/main/java/org/mvplugins/multiverse/core/world/options/RegenWorldOptions.java

View workflow job for this annotation

GitHub Actions / checkstyle / checkstyle

[checkstyle] reported by reviewdog 🐶 Wrong order for 'java.util.Collections' import. Raw Output: /github/workspace/./src/main/java/org/mvplugins/multiverse/core/world/options/RegenWorldOptions.java:10:1: warning: Wrong order for 'java.util.Collections' import. (com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck)
import java.util.List;

/**
* Options for customizing the regeneration of a world.
*/
Expand All @@ -31,6 +34,7 @@
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 @@
}
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
Loading