Skip to content
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

Better purging #2541

Merged
merged 10 commits into from
Oct 25, 2024
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
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
Loading