Skip to content

Commit

Permalink
Merge pull request #2541 from BentoBoxWorld/better_purging
Browse files Browse the repository at this point in the history
Better purging
  • Loading branch information
tastybento authored Oct 25, 2024
2 parents 9a24ee9 + 2cb0651 commit c5b1a90
Show file tree
Hide file tree
Showing 27 changed files with 321 additions and 69 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@
<postgresql.version>42.2.18</postgresql.version>
<hikaricp.version>5.0.1</hikaricp.version>
<!-- More visible way to change dependency versions -->
<spigot.version>1.20.5-R0.1-SNAPSHOT</spigot.version>
<spigot.version>1.21.3-R0.1-SNAPSHOT</spigot.version>
<!-- Might differ from the last Spigot release for short periods
of time -->
<paper.version>1.20.6-R0.1-SNAPSHOT</paper.version>
<paper.version>1.21.1-R0.1-SNAPSHOT</paper.version>
<bstats.version>3.0.0</bstats.version>
<vault.version>1.7.1</vault.version>
<placeholderapi.version>2.10.9</placeholderapi.version>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/world/bentobox/bentobox/BentoBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ private void completeSetup(long loadTime) {
registerListeners();

// Load islands from database - need to wait until all the worlds are loaded
log("Loading islands from database...");
try {
islandsManager.load();
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
Expand All @@ -21,8 +23,10 @@

public class AdminPurgeCommand extends CompositeCommand implements Listener {

private static final Long YEAR2000 = 946713600L;
private int count;
private boolean inPurge;
private boolean scanning;
private boolean toBeConfirmed;
private Iterator<String> it;
private User user;
Expand All @@ -47,6 +51,10 @@ public void setup() {

@Override
public boolean canExecute(User user, String label, List<String> args) {
if (scanning) {
user.sendMessage("commands.admin.purge.scanning-in-progress");
return false;
}
if (inPurge) {
user.sendMessage("commands.admin.purge.purge-in-progress", TextVariables.LABEL, this.getTopLabel());
return false;
Expand Down Expand Up @@ -75,13 +83,21 @@ public boolean execute(User user, String label, List<String> args) {
user.sendMessage("commands.admin.purge.days-one-or-more");
return false;
}
islands = getOldIslands(days);
user.sendMessage("commands.admin.purge.purgable-islands", TextVariables.NUMBER, String.valueOf(islands.size()));
if (!islands.isEmpty()) {
toBeConfirmed = true;
user.sendMessage("commands.admin.purge.confirm", TextVariables.LABEL, this.getTopLabel());
return false;
}
user.sendMessage("commands.admin.purge.scanning");
scanning = true;
getOldIslands(days).thenAccept(islandSet -> {
user.sendMessage("commands.admin.purge.purgable-islands", TextVariables.NUMBER,
String.valueOf(islandSet.size()));
if (!islandSet.isEmpty()) {
toBeConfirmed = true;
user.sendMessage("commands.admin.purge.confirm", TextVariables.LABEL, this.getTopLabel());
islands = islandSet;
} else {
user.sendMessage("commands.admin.purge.none-found");
}
scanning = false;
});

} catch (NumberFormatException e) {
user.sendMessage("commands.admin.purge.number-error");
return false;
Expand Down Expand Up @@ -125,40 +141,61 @@ void onIslandDeleted(IslandDeletedEvent e) {
* @param days days
* @return set of islands
*/
Set<String> getOldIslands(int days) {
long currentTimeMillis = System.currentTimeMillis();
long daysInMilliseconds = (long) days * 1000 * 3600 * 24;
Set<String> oldIslands = new HashSet<>();

CompletableFuture<Set<String>> getOldIslands(int days) {
CompletableFuture<Set<String>> result = new CompletableFuture<>();
// Process islands in one pass, logging and adding to the set if applicable
getPlugin().getIslands().getIslands().stream()
getPlugin().getIslands().getIslandsASync().thenAccept(list -> {
user.sendMessage("commands.admin.purge.total-islands", TextVariables.NUMBER, String.valueOf(list.size()));
Set<String> oldIslands = new HashSet<>();
list.stream()
.filter(i -> !i.isSpawn()).filter(i -> !i.getPurgeProtected())
.filter(i -> i.getWorld() != null) // to handle currently unloaded world islands
.filter(i -> i.getWorld().equals(this.getWorld())).filter(Island::isOwned).filter(
i -> i.getMemberSet().stream()
.allMatch(member -> (currentTimeMillis
- Bukkit.getOfflinePlayer(member).getLastPlayed()) > daysInMilliseconds))
.filter(i -> i.getWorld().equals(this.getWorld())) // Island needs to be in this world
.filter(Island::isOwned) // The island needs to be owned
.filter(i -> i.getMemberSet().stream().allMatch(member -> checkLastLoginTimestamp(days, member)))
.forEach(i -> {
// Add the unique island ID to the set
oldIslands.add(i.getUniqueId());
BentoBox.getInstance().log("Will purge island at " + Util.xyz(i.getCenter().toVector()) + " in "
getPlugin().log("Will purge island at " + Util.xyz(i.getCenter().toVector()) + " in "
+ i.getWorld().getName());
// Log each member's last login information
i.getMemberSet().forEach(member -> {
Date lastLogin = new Date(Bukkit.getOfflinePlayer(member).getLastPlayed());
Long timestamp = getPlayers().getLastLoginTimestamp(member);
Date lastLogin = new Date(timestamp);
BentoBox.getInstance()
.log("Player " + BentoBox.getInstance().getPlayers().getName(member)
+ " last logged in "
+ (int) ((currentTimeMillis - Bukkit.getOfflinePlayer(member).getLastPlayed())
/ 1000 / 3600 / 24)
+ (int) ((System.currentTimeMillis() - timestamp) / 1000 / 3600 / 24)
+ " days ago. " + lastLogin);
});
BentoBox.getInstance().log("+-----------------------------------------+");
});
result.complete(oldIslands);
});
return result;
}

return oldIslands;
private boolean checkLastLoginTimestamp(int days, UUID member) {
long daysInMilliseconds = days * 24L * 3600 * 1000; // Calculate days in milliseconds
Long lastLoginTimestamp = getPlayers().getLastLoginTimestamp(member);
// If no valid last login time is found or it's before the year 2000, try to fetch from Bukkit
if (lastLoginTimestamp == null || lastLoginTimestamp < YEAR2000) {
lastLoginTimestamp = Bukkit.getOfflinePlayer(member).getLastPlayed();

// If still invalid, set the current timestamp to mark the user for eventual purging
if (lastLoginTimestamp < YEAR2000) {
getPlayers().setLoginTimeStamp(member, System.currentTimeMillis());
return false; // User will be purged in the future
} else {
// Otherwise, update the last login timestamp with the valid value from Bukkit
getPlayers().setLoginTimeStamp(member, lastLoginTimestamp);
}
}
// Check if the difference between now and the last login is greater than the allowed days
return System.currentTimeMillis() - lastLoginTimestamp > daysInMilliseconds;
}


/**
* @return the inPurge
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,31 @@ protected AbstractDatabaseHandler() {}
@Nullable
public abstract T loadObject(@NonNull String uniqueId) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException;

/**
* Loads all the records in this table and returns a list of them async
* @return CompletableFuture List of <T>
* @since 2.7.0
*/
public CompletableFuture<List<T>> loadObjectsASync() {
CompletableFuture<List<T>> completableFuture = new CompletableFuture<>();

Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(), () -> {
try {
completableFuture.complete(loadObjects()); // Complete the future with the result
} catch (Exception e) {
completableFuture.completeExceptionally(e); // Complete exceptionally if an error occurs
plugin.logError("Failed to load objects asynchronously: " + e.getMessage());
}
});

return completableFuture;
}

/**
* Save T into the corresponding database
*
* @param instance that should be inserted into the database
* @return completable future that is true if saved
*/
public abstract CompletableFuture<Boolean> saveObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException ;

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/world/bentobox/bentobox/database/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ public static Set<Class<? extends DataObject>> getDataobjects() {
return dataObjects;
}

/**
* Load all objects async
* @return CompletableFuture<List<T>>
*/
public @NonNull CompletableFuture<List<T>> loadObjectsASync() {
return handler.loadObjectsASync();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.entity.Player;
Expand Down Expand Up @@ -1512,7 +1513,7 @@ public void setGameMode(String gameMode) {
*/
public boolean hasNetherIsland() {
World nether = BentoBox.getInstance().getIWM().getNetherWorld(getWorld());
return nether != null && !getCenter().toVector().toLocation(nether).getBlock().getType().isAir();
return nether != null && (getCenter().toVector().toLocation(nether).getBlock().getType() != Material.AIR);
}

/**
Expand All @@ -1536,7 +1537,7 @@ public boolean isNetherIslandEnabled() {
*/
public boolean hasEndIsland() {
World end = BentoBox.getInstance().getIWM().getEndWorld(getWorld());
return end != null && !getCenter().toVector().toLocation(end).getBlock().getType().isAir();
return end != null && (getCenter().toVector().toLocation(end).getBlock().getType() != Material.AIR);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;

import com.google.gson.annotations.Expose;

Expand Down Expand Up @@ -37,6 +38,8 @@ public class Players implements DataObject, MetaDataAble {
private String locale = "";
@Expose
private Map<String, Integer> deaths = new HashMap<>();
@Expose
private Long lastLogin;

/**
* This variable stores set of worlds where user inventory must be cleared.
Expand Down Expand Up @@ -292,5 +295,20 @@ public void setMetaData(Map<String, MetaDataValue> metaData) {
this.metaData = metaData;
}

/**
* @return the lastLogin, Unix timestamp, or null if never logged in since this was tracked
* @since 2.6.0
*/
@Nullable
public Long getLastLogin() {
return lastLogin;
}

/**
* @param lastLogin the lastLogin to set
*/
public void setLastLogin(Long lastLogin) {
this.lastLogin = lastLogin;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public void onPlayerJoin(final PlayerJoinEvent event) {
// don't exist
players.getPlayer(playerUUID);

// Set the login
players.setLoginTimeStamp(user);

// Reset island resets if required
plugin.getIWM().getOverWorlds().stream()
.filter(w -> event.getPlayer().getLastPlayed() < plugin.getIWM().getResetEpoch(w))
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ public Collection<Island> getIslands() {
return handler.loadObjects().stream().toList();
}

/**
* Loads all existing islands from the database without caching async
*
* @return CompletableFuture<List> of every island
* @since 2.7.0
*/
public CompletableFuture<List<Island>> getIslandsASync() {
return handler.loadObjectsASync();
}

/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those who may be unowned) in the specified world.
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/world/bentobox/bentobox/managers/PlayersManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,44 @@ public CompletableFuture<Boolean> savePlayer(UUID uuid) {
return CompletableFuture.completedFuture(false);
}

/**
* Records when the user last logged in. Called by the joinleave listener
* @param user user
* @since 2.7.0
*/
public void setLoginTimeStamp(User user) {
if (user.isPlayer() && user.isOnline()) {
setLoginTimeStamp(user.getUniqueId(), System.currentTimeMillis());
}
}

/**
* Set the player's last login time to a timestamp
* @param playerUUID player UUID
* @param timestamp timestamp to set
* @since 2.7.0
*/
public void setLoginTimeStamp(UUID playerUUID, long timestamp) {
Players p = this.getPlayer(playerUUID);
if (p != null) {
p.setLastLogin(timestamp);
this.savePlayer(playerUUID);
}
}

/**
* Get the last login time stamp for this player
* @param uuid player's UUID
* @return timestamp or null if unknown or not recorded yet
* @since 2.7.0
*/
@Nullable
public Long getLastLoginTimestamp(UUID uuid) {
Players p = this.getPlayer(uuid);
if (p != null) {
return p.getLastLogin();
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package world.bentobox.bentobox.nms.v1_21_2_R0_1_SNAPSHOT;

/**
* Same as 1.21
*/
public class PasteHandlerImpl extends world.bentobox.bentobox.nms.v1_21_R0_1_SNAPSHOT.PasteHandlerImpl {
// Do nothing special
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package world.bentobox.bentobox.nms.v1_21_2_R0_1_SNAPSHOT;

/**
* Same as 1.21
*/
public class WorldRegeneratorImpl extends world.bentobox.bentobox.nms.v1_21_R0_1_SNAPSHOT.WorldRegeneratorImpl {
// Do nothing special
}
4 changes: 4 additions & 0 deletions src/main/resources/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ commands:
purgable-islands: '&a Found &b [number] &a purgable islands.'
purge-in-progress: '&c Purging in progress. Use &b /[label] purge stop &c to
cancel.'
scanning: '&a Scanning islands in the database. This may take a while depending on how many you have...'
scanning-in-progress: '&c Scanning in progress, please wait'
none-found: '&c No islands found to purge.'
total-islands: '&a You have [number] islands in your database in all worlds.'
number-error: '&c Argument must be a number of days'
confirm: '&d Type &b /[label] purge confirm &d to start purging'
completed: '&a Purging stopped.'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,14 @@ public void checkSpigotMessage(String expectedMessage, int expectedOccurrences)
*/
public EntityExplodeEvent getExplodeEvent(Entity entity, Location l, List<Block> list) {
//return new EntityExplodeEvent(entity, l, list, 0, null);
return new EntityExplodeEvent(entity, l, list, 0);
return new EntityExplodeEvent(entity, l, list, 0, null);
}

public PlayerDeathEvent getPlayerDeathEvent(Player player, List<ItemStack> drops, int droppedExp, int newExp,
int newTotalExp, int newLevel, @Nullable String deathMessage) {
//return new PlayerDeathEvent(player, null, drops, droppedExp, newExp, newTotalExp, newLevel, deathMessage);
return new PlayerDeathEvent(player, drops, droppedExp, newExp, newTotalExp, newLevel, deathMessage);
//Technically this null is not allowed, but it works right now
return new PlayerDeathEvent(player, null, drops, droppedExp, newExp,
newTotalExp, newLevel, deathMessage);
}

}
Loading

0 comments on commit c5b1a90

Please sign in to comment.