Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bd23874
Phase 1: Add housekeeping scheduler + extract PurgeRegionsService
tastybento Apr 9, 2026
da21fea
Fix async World.save() crash in purge delete path
tastybento Apr 9, 2026
9c90160
Log explicit save messages around purge world saves
tastybento Apr 9, 2026
6bc7ae3
Add admin purge age-regions test helper
tastybento Apr 9, 2026
4ec87f8
Merge remote-tracking branch 'origin/develop' into worktree-feature-p…
tastybento Apr 9, 2026
9f33f64
Phase 2: Reset always soft-deletes via deletable flag
tastybento Apr 9, 2026
0a515c2
Surface soft-deleted islands to admins
tastybento Apr 9, 2026
73a3240
Phase 3: Split AdminDeleteCommand on isUsesNewChunkGeneration
tastybento Apr 9, 2026
23bd5cb
Phase 3.5: Add /bbox admin purge deleted + daily housekeeping sweep
tastybento Apr 9, 2026
93c35cc
Merge remote-tracking branch 'origin/develop' into worktree-feature-p…
tastybento Apr 9, 2026
a63da7c
Defer deleted-sweep island DB removal to plugin shutdown
tastybento Apr 10, 2026
107e56f
Remove verbose per-file debug logging from deleteRegionFiles
tastybento Apr 10, 2026
8fd252a
Remove keep-previous-island-on-reset setting
tastybento Apr 10, 2026
9aa7259
Bump version to 4.0.0
tastybento Apr 11, 2026
54c6ca3
Merge branch 'develop' into worktree-feature-purge-regions-reset
tastybento Apr 11, 2026
1a369e4
Update src/main/java/world/bentobox/bentobox/managers/PurgeRegionsSer…
tastybento Apr 11, 2026
9e84fe9
Update src/main/java/world/bentobox/bentobox/api/commands/admin/purge…
tastybento Apr 11, 2026
a10199f
Update src/main/java/world/bentobox/bentobox/api/commands/admin/Admin…
tastybento Apr 11, 2026
204d6c5
Address PR review: fix typo, error messages, scheduler, quit cleanup
Copilot Apr 11, 2026
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
group = "world.bentobox" // From <groupId>

// Base properties from <properties>
val buildVersion = "3.14.1"
val buildVersion = "4.0.0"
val buildNumberDefault = "-LOCAL" // Local build identifier
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version

Expand Down
37 changes: 37 additions & 0 deletions src/main/java/world/bentobox/bentobox/BentoBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.ChunkPregenManager;
import world.bentobox.bentobox.managers.HousekeepingManager;
import world.bentobox.bentobox.managers.IslandDeletionManager;
import world.bentobox.bentobox.managers.PurgeRegionsService;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
Expand Down Expand Up @@ -72,6 +74,8 @@ public class BentoBox extends JavaPlugin implements Listener {
private MapManager mapManager;
private IslandDeletionManager islandDeletionManager;
private ChunkPregenManager chunkPregenManager;
private PurgeRegionsService purgeRegionsService;
private HousekeepingManager housekeepingManager;
private WebManager webManager;

// Settings
Expand Down Expand Up @@ -143,6 +147,9 @@ public void onEnable(){
}
islandsManager = new IslandsManager(this);

// Shared purge-regions logic (command + housekeeping)
purgeRegionsService = new PurgeRegionsService(this);

// Start head getter
headGetter = new HeadGetter(this);

Expand Down Expand Up @@ -233,6 +240,10 @@ private void completeSetup(long loadTime) {

webManager = new WebManager(this);

// Housekeeping: auto-purge of unused region files (default OFF)
housekeepingManager = new HousekeepingManager(this);
housekeepingManager.start();

final long enableTime = System.currentTimeMillis() - enableStart;

// Show banner
Expand Down Expand Up @@ -308,6 +319,15 @@ public void onDisable() {
if (chunkPregenManager != null) {
chunkPregenManager.shutdown();
}
// Flush deferred island deletions from the deleted-sweep purge.
// Paper's internal chunk cache is cleared on shutdown, so the stale
// in-memory chunks are guaranteed gone at this point.
if (purgeRegionsService != null) {
purgeRegionsService.flushPendingDeletions();
}
if (housekeepingManager != null) {
housekeepingManager.stop();
}

}

Expand Down Expand Up @@ -566,6 +586,23 @@ public ChunkPregenManager getChunkPregenManager() {
return chunkPregenManager;
}

/**
* @return the shared {@link PurgeRegionsService} used by the purge
* regions command and the housekeeping scheduler.
* @since 3.14.0
*/
public PurgeRegionsService getPurgeRegionsService() {
return purgeRegionsService;
}

/**
* @return the {@link HousekeepingManager}, or {@code null} if not yet initialized.
* @since 3.14.0
*/
public HousekeepingManager getHousekeepingManager() {
return housekeepingManager;
}

/**
* @return an optional of the Bstats instance
* @since 1.1
Expand Down
152 changes: 111 additions & 41 deletions src/main/java/world/bentobox/bentobox/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,27 +319,45 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.delete-speed", since = "1.7.0")
private int deleteSpeed = 1;

// Island deletion related settings
@ConfigComment("Toggles whether islands, when players are resetting them, should be kept in the world or deleted.")
@ConfigComment("* If set to 'true', whenever a player resets his island, his previous island will become unowned and won't be deleted from the world.")
@ConfigComment(" You can, however, still delete those unowned islands through purging.")
@ConfigComment(" On bigger servers, this can lead to an increasing world size.")
@ConfigComment(" Yet, this allows admins to retrieve a player's old island in case of an improper use of the reset command.")
@ConfigComment(" Admins can indeed re-add the player to his old island by registering him to it.")
@ConfigComment("* If set to 'false', whenever a player resets his island, his previous island will be deleted from the world.")
@ConfigComment(" This is the default behaviour.")
@ConfigEntry(path = "island.deletion.keep-previous-island-on-reset", since = "1.13.0")
private boolean keepPreviousIslandOnReset = false;

@ConfigComment("Toggles how the islands are deleted.")
@ConfigComment("* If set to 'false', all islands will be deleted at once.")
@ConfigComment(" This is fast but may cause an impact on the performance")
@ConfigComment(" as it'll load all the chunks of the in-deletion islands.")
@ConfigComment("* If set to 'true', the islands will be deleted one by one.")
@ConfigComment(" This is slower but will not cause any impact on the performance.")
@ConfigEntry(path = "island.deletion.slow-deletion", since = "1.19.1")

/**
* @deprecated No longer bound to config. The chunk-by-chunk deletion
* pipeline has been removed. Slated for removal.
*/
@Deprecated(since = "3.14.0", forRemoval = true)
private boolean slowDeletion = false;

// Island deletion housekeeping
@ConfigComment("Housekeeping: periodic auto-purge of unused region files.")
@ConfigComment("When a player resets their island, the old island is no longer physically")
@ConfigComment("deleted. Instead it is orphaned (marked deletable) and the region files on")
@ConfigComment("disk are reclaimed later by a scheduled purge. This avoids the brittle")
@ConfigComment("chunk-copy mechanism that required pristine seed worlds.")
@ConfigComment("")
@ConfigComment("WARNING: housekeeping deletes .mca region files from disk. It uses the same")
@ConfigComment("protections as the /bbox purge regions command (online players, island level,")
@ConfigComment("purge-protected flag, spawn islands, unowned-but-not-deletable islands are all")
@ConfigComment("skipped) but is destructive by design. Default is OFF.")
@ConfigComment("Enable the scheduled housekeeping task.")
@ConfigEntry(path = "island.deletion.housekeeping.enabled", since = "3.14.0")
private boolean housekeepingEnabled = false;

@ConfigComment("How often the housekeeping task runs, in days.")
@ConfigEntry(path = "island.deletion.housekeeping.interval-days", since = "3.14.0")
private int housekeepingIntervalDays = 30;

@ConfigComment("Minimum age (in days) of region files considered for purge. Passed to the")
@ConfigComment("same scanner the /bbox purge regions command uses.")
@ConfigEntry(path = "island.deletion.housekeeping.region-age-days", since = "3.14.0")
private int housekeepingRegionAgeDays = 60;

@ConfigComment("How often the deleted-islands sweep runs, in hours. This reaps region")
@ConfigComment("files for any island already flagged as deletable (e.g. from /is reset)")
@ConfigComment("and is independent of the age-based sweep above. Set to 0 to disable")
@ConfigComment("the deleted sweep while leaving the age sweep running.")
@ConfigEntry(path = "island.deletion.housekeeping.deleted-interval-hours", since = "3.14.0")
private int housekeepingDeletedIntervalHours = 24;

// Chunk pre-generation settings
@ConfigComment("")
@ConfigComment("Chunk pre-generation settings.")
Expand Down Expand Up @@ -826,28 +844,6 @@ public void setDatabasePrefix(String databasePrefix) {
this.databasePrefix = databasePrefix;
}

/**
* Returns whether islands, when reset, should be kept or deleted.
*
* @return {@code true} if islands, when reset, should be kept; {@code false}
* otherwise.
* @since 1.13.0
*/
public boolean isKeepPreviousIslandOnReset() {
return keepPreviousIslandOnReset;
}

/**
* Sets whether islands, when reset, should be kept or deleted.
*
* @param keepPreviousIslandOnReset {@code true} if islands, when reset, should
* be kept; {@code false} otherwise.
* @since 1.13.0
*/
public void setKeepPreviousIslandOnReset(boolean keepPreviousIslandOnReset) {
this.keepPreviousIslandOnReset = keepPreviousIslandOnReset;
}

/**
* Returns a MongoDB client connection URI to override default connection
* options.
Expand Down Expand Up @@ -1014,7 +1010,11 @@ public void setSafeSpotSearchVerticalRange(int safeSpotSearchVerticalRange) {
* Is slow deletion boolean.
*
* @return the boolean
* @deprecated The chunk-by-chunk deletion pipeline is being removed. This
* setting no longer has any effect and will be deleted in a
* future release. Configure the housekeeping auto-purge instead.
*/
@Deprecated(since = "3.14.0", forRemoval = true)
public boolean isSlowDeletion() {
return slowDeletion;
}
Expand All @@ -1023,11 +1023,81 @@ public boolean isSlowDeletion() {
* Sets slow deletion.
*
* @param slowDeletion the slow deletion
* @deprecated See {@link #isSlowDeletion()}.
*/
@Deprecated(since = "3.14.0", forRemoval = true)
public void setSlowDeletion(boolean slowDeletion) {
this.slowDeletion = slowDeletion;
}

/**
* @return whether the periodic housekeeping task (auto-purge of unused
* region files) is enabled.
* @since 3.14.0
*/
public boolean isHousekeepingEnabled() {
return housekeepingEnabled;
}

/**
* @param housekeepingEnabled whether the periodic housekeeping task is enabled.
* @since 3.14.0
*/
public void setHousekeepingEnabled(boolean housekeepingEnabled) {
this.housekeepingEnabled = housekeepingEnabled;
}

/**
* @return how often the housekeeping task runs, in days.
* @since 3.14.0
*/
public int getHousekeepingIntervalDays() {
return housekeepingIntervalDays;
}

/**
* @param housekeepingIntervalDays how often the housekeeping task runs, in days.
* @since 3.14.0
*/
public void setHousekeepingIntervalDays(int housekeepingIntervalDays) {
this.housekeepingIntervalDays = housekeepingIntervalDays;
}

/**
* @return minimum age (in days) of region files considered for auto-purge.
* @since 3.14.0
*/
public int getHousekeepingRegionAgeDays() {
return housekeepingRegionAgeDays;
}

/**
* @param housekeepingRegionAgeDays minimum age (in days) of region files
* considered for auto-purge.
* @since 3.14.0
*/
public void setHousekeepingRegionAgeDays(int housekeepingRegionAgeDays) {
this.housekeepingRegionAgeDays = housekeepingRegionAgeDays;
}

/**
* @return how often the deleted-islands sweep runs, in hours. {@code 0}
* disables the deleted sweep.
* @since 3.14.0
*/
public int getHousekeepingDeletedIntervalHours() {
return housekeepingDeletedIntervalHours;
}

/**
* @param housekeepingDeletedIntervalHours how often the deleted sweep runs
* in hours. {@code 0} disables it.
* @since 3.14.0
*/
public void setHousekeepingDeletedIntervalHours(int housekeepingDeletedIntervalHours) {
this.housekeepingDeletedIntervalHours = housekeepingDeletedIntervalHours;
}

/**
* @return whether chunk pre-generation is enabled
* @since 3.14.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.eclipse.jdt.annotation.Nullable;

import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.commands.island.IslandGoCommand;
Expand All @@ -17,6 +18,8 @@
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.util.DeleteIslandChunks;
import world.bentobox.bentobox.util.Util;

public class AdminDeleteCommand extends ConfirmableCommand {
Expand Down Expand Up @@ -112,8 +115,30 @@ private void deleteIsland(User user, Island oldIsland) {
.oldIsland(oldIsland).location(oldIsland.getCenter()).build();
user.sendMessage("commands.admin.delete.deleted-island", TextVariables.XYZ,
Util.xyz(oldIsland.getCenter().toVector()));
getIslands().deleteIsland(oldIsland, true, targetUUID);

// Branch on how the gamemode generates its chunks.
//
// - New chunk generation (e.g. Boxed): chunks are expensive to
// recreate, so the island is soft-deleted (marked deletable,
// left in place) and PurgeRegionsService / HousekeepingManager
// reaps the region files and DB row later on its schedule.
//
// - Simple/void generation: chunks are cheap — hard-delete the
// island first so the cancellable delete path can veto the
// operation before any chunk work starts, then repaint them via
// the addon's own ChunkGenerator using the existing
// DeleteIslandChunks + WorldRegenerator.regenerateSimple path.
//
// If we can't resolve the gamemode, default to soft-delete.
GameModeAddon gm = getIWM().getAddon(getWorld()).orElse(null);
if (gm != null && !gm.isUsesNewChunkGeneration()) {
getIslands().hardDeleteIsland(oldIsland);
// DeleteIslandChunks snapshots the island bounds from oldIsland,
// so it can safely run after the row has been hard-deleted.
new DeleteIslandChunks(getPlugin(), new IslandDeletion(oldIsland));
} else {
getIslands().deleteIsland(oldIsland, true, targetUUID);
}
}

private void deletePlayer(User user) {
Expand Down
Loading