Merge pull request #466 from BentoBoxWorld/IslandDelete

Island delete and async saving for YAML and MySQL
This commit is contained in:
tastybento 2019-01-13 07:59:39 -08:00 committed by GitHub
commit 6557d2fad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 696 additions and 353 deletions

View File

@ -25,6 +25,7 @@ import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.IslandDeletionManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
@ -67,6 +68,8 @@ public class BentoBox extends JavaPlugin {
private boolean isLoaded;
private IslandDeletionManager islandDeletionManager;
@Override
public void onEnable(){
if (!ServerCompatibility.getInstance().checkCompatibility(this).isCanLaunch()) {
@ -142,10 +145,10 @@ public class BentoBox extends JavaPlugin {
// Load islands from database - need to wait until all the worlds are loaded
islandsManager.load();
// Save islands & players data asynchronously every X minutes
// Save islands & players data every X minutes
instance.getServer().getScheduler().runTaskTimer(instance, () -> {
playersManager.save(true);
islandsManager.save(true);
playersManager.saveAll();
islandsManager.saveAll();
}, getSettings().getDatabaseBackupPeriod() * 20 * 60L, getSettings().getDatabaseBackupPeriod() * 20 * 60L);
// Make sure all flag listeners are registered.
@ -169,13 +172,15 @@ public class BentoBox extends JavaPlugin {
// Setup the Placeholders manager
placeholdersManager = new PlaceholdersManager(this);
// Fire plugin ready event
isLoaded = true;
Bukkit.getServer().getPluginManager().callEvent(new BentoBoxReadyEvent());
// Show banner
User.getInstance(Bukkit.getConsoleSender()).sendMessage("successfully-loaded",
TextVariables.VERSION, instance.getDescription().getVersion(),
"[time]", String.valueOf(System.currentTimeMillis() - startMillis));
// Fire plugin ready event - this should go last after everything else
isLoaded = true;
Bukkit.getServer().getPluginManager().callEvent(new BentoBoxReadyEvent());
});
}
@ -196,6 +201,9 @@ public class BentoBox extends JavaPlugin {
manager.registerEvents(new BannedVisitorCommands(this), this);
// Death counter
manager.registerEvents(new DeathListener(this), this);
// Island Delete Manager
islandDeletionManager = new IslandDeletionManager(this);
manager.registerEvents(islandDeletionManager, this);
}
@Override
@ -356,4 +364,11 @@ public class BentoBox extends JavaPlugin {
public PlaceholdersManager getPlaceholdersManager() {
return placeholdersManager;
}
/**
* @return the islandDeletionManager
*/
public IslandDeletionManager getIslandDeletionManager() {
return islandDeletionManager;
}
}

View File

@ -51,6 +51,12 @@ public class AdminRegisterCommand extends ConfirmableCommand {
return false;
}
// Check if this spot is still being deleted
Location closestIsland = getClosestIsland(user.getLocation());
if (getPlugin().getIslandDeletionManager().inDeletion(closestIsland)) {
user.sendMessage("commands.admin.register.in-deletion");
return false;
}
// Check if island is owned
Optional<Island> island = getIslands().getIslandAt(user.getLocation());
if (island.map(i -> i.getOwner() != null).orElse(false)) {
@ -69,7 +75,7 @@ public class AdminRegisterCommand extends ConfirmableCommand {
user.sendMessage("commands.admin.register.no-island-here");
this.askConfirmation(user, () -> {
// Make island here
Island i = getIslands().createIsland(getClosestIsland(user.getLocation()), targetUUID);
Island i = getIslands().createIsland(closestIsland, targetUUID);
getIslands().setOwner(user, targetUUID, i);
getWorld().getBlockAt(i.getCenter()).setType(Material.BEDROCK);
user.sendMessage("commands.admin.register.registered-island", "[xyz]", Util.xyz(i.getCenter().toVector()));

View File

@ -1,12 +1,15 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import world.bentobox.bentobox.api.commands.CompositeCommand;
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.util.Util;
public class AdminRangeResetCommand extends CompositeCommand {
@ -50,4 +53,15 @@ public class AdminRangeResetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -1,6 +1,8 @@
package world.bentobox.bentobox.api.commands.admin.range;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
@ -9,6 +11,7 @@ import world.bentobox.bentobox.api.commands.CompositeCommand;
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.util.Util;
public class AdminRangeSetCommand extends CompositeCommand {
@ -72,4 +75,15 @@ public class AdminRangeSetCommand extends CompositeCommand {
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size()-1) : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
}
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -106,7 +106,7 @@ public class IslandTeamInviteAcceptCommand extends CompositeCommand {
if (inviter != null) {
inviter.sendMessage("commands.island.team.invite.accept.name-joined-your-island", TextVariables.NAME, user.getName());
}
getIslands().save(false);
getIslands().save(island);
return true;
}

View File

@ -10,6 +10,7 @@ import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
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.util.Util;
public class IslandTeamSetownerCommand extends CompositeCommand {
@ -59,9 +60,9 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false;
}
// Fire event so add-ons can run commands, etc.
Island island = getIslands().getIsland(getWorld(), playerUUID);
IslandBaseEvent event = TeamEvent.builder()
.island(getIslands()
.getIsland(getWorld(), playerUUID))
.island(island)
.reason(TeamEvent.Reason.SETOWNER)
.involvedPlayer(targetUUID)
.build();
@ -70,7 +71,7 @@ public class IslandTeamSetownerCommand extends CompositeCommand {
return false;
}
getIslands().setOwner(getWorld(), user, targetUUID);
getIslands().save(true);
getIslands().save(island);
return true;
}

View File

@ -43,7 +43,7 @@ public class IslandBaseEvent extends PremadeEvent implements Cancellable {
}
/**
* @return the island involved in this event
* @return the island involved in this event. This may be null in the case of deleted islands, so use location instead
*/
public Island getIsland(){
return island;

View File

@ -6,6 +6,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Location;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
@ -58,9 +59,14 @@ public class IslandEvent extends IslandBaseEvent {
*/
CREATED,
/**
* Fired just before any island chunks are to be deleted.
* Fired when an island is to be deleted. Note an island can be deleted without having
* chunks removed.
*/
DELETE,
/**
* Fired when island chunks are going to be deleted
*/
DELETE_CHUNKS,
/**
* Fired after all island chunks have been deleted or set for regeneration by the server
*/
@ -168,14 +174,39 @@ public class IslandEvent extends IslandBaseEvent {
super(island, player, admin, location);
}
}
/**
* Fired when an island chunks are going to be deleted.
* May be cancelled.
*
*/
public static class IslandDeleteChunksEvent extends IslandBaseEvent {
private final IslandDeletion deletedIslandInfo;
private IslandDeleteChunksEvent(Island island, UUID player, boolean admin, Location location, IslandDeletion deletedIsland) {
// Final variables have to be declared in the constructor
super(island, player, admin, location);
this.deletedIslandInfo = deletedIsland;
}
public IslandDeletion getDeletedIslandInfo() {
return deletedIslandInfo;
}
}
/**
* Fired when an island is deleted.
*
*/
public static class IslandDeletedEvent extends IslandBaseEvent {
private IslandDeletedEvent(Island island, UUID player, boolean admin, Location location) {
private final IslandDeletion deletedIslandInfo;
private IslandDeletedEvent(Island island, UUID player, boolean admin, Location location, IslandDeletion deletedIsland) {
// Final variables have to be declared in the constructor
super(island, player, admin, location);
this.deletedIslandInfo = deletedIsland;
}
public IslandDeletion getDeletedIslandInfo() {
return deletedIslandInfo;
}
}
/**
@ -256,6 +287,7 @@ public class IslandEvent extends IslandBaseEvent {
private Reason reason = Reason.UNKNOWN;
private boolean admin;
private Location location;
private IslandDeletion deletedIslandInfo;
public IslandEventBuilder island(Island island) {
this.island = island;
@ -295,6 +327,11 @@ public class IslandEvent extends IslandBaseEvent {
return this;
}
public IslandEventBuilder deletedIslandInfo(IslandDeletion deletedIslandInfo) {
this.deletedIslandInfo = deletedIslandInfo;
return this;
}
public IslandBaseEvent build() {
// Call the generic event for developers who just want one event and use the Reason enum
Bukkit.getServer().getPluginManager().callEvent(new IslandEvent(island, player, admin, location, reason));
@ -316,8 +353,12 @@ public class IslandEvent extends IslandBaseEvent {
IslandDeleteEvent delete = new IslandDeleteEvent(island, player, admin, location);
Bukkit.getServer().getPluginManager().callEvent(delete);
return delete;
case DELETE_CHUNKS:
IslandDeleteChunksEvent deleteChunks = new IslandDeleteChunksEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleteChunks);
return deleteChunks;
case DELETED:
IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location);
IslandDeletedEvent deleted = new IslandDeletedEvent(island, player, admin, location, deletedIslandInfo);
Bukkit.getServer().getPluginManager().callEvent(deleted);
return deleted;
case ENTER:

View File

@ -4,6 +4,8 @@ import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
@ -38,12 +40,14 @@ public abstract class AbstractDatabaseHandler<T> {
/**
* The addon that is accessing the database, if any.
*/
@Nullable
private Addon addon;
/**
* Get the addon that is accessing the database, if any. May be null.
* @return the addon
*/
@Nullable
public Addon getAddon() {
return addon;
}
@ -52,7 +56,7 @@ public abstract class AbstractDatabaseHandler<T> {
* Set the addon that is accessing the database, if any.
* @param addon the addon to set
*/
public void setAddon(Addon addon) {
public void setAddon(@Nullable Addon addon) {
this.addon = addon;
}
@ -84,7 +88,8 @@ public abstract class AbstractDatabaseHandler<T> {
* @param uniqueId - unique ID
* @return <T>
*/
public abstract T loadObject(String uniqueId) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException;
@Nullable
public abstract T loadObject(@NonNull String uniqueId) throws InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IntrospectionException, NoSuchMethodException;
/**
* Save T into the corresponding database
@ -110,4 +115,12 @@ public abstract class AbstractDatabaseHandler<T> {
* Closes the database
*/
public abstract void close();
/**
* Attempts to delete the object with the uniqueId
* @param uniqueId - uniqueId of object
* @return true if successful, false if there is no such uniqueId
* @since 1.1
*/
public abstract boolean deleteID(String uniqueId);
}

View File

@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.eclipse.jdt.annotation.NonNull;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
@ -44,6 +45,7 @@ public class Database<T> {
* Load all the config objects and supply them as a list
* @return list of config objects or an empty list if they cannot be loaded
*/
@NonNull
public List<T> loadObjects() {
List<T> result = new ArrayList<>();
try {
@ -100,6 +102,16 @@ public class Database<T> {
return handler.objectExists(name);
}
/**
* Attempts to delete the object with the uniqueId
* @param uniqueId - uniqueId of object
* @return true if successful, false if there is no such uniqueId
* @since 1.1
*/
public boolean deleteID(String uniqueId) {
return handler.deleteID(uniqueId);
}
/**
* Delete object from database
* @param object - object to delete
@ -109,7 +121,7 @@ public class Database<T> {
handler.deleteObject(object);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| IntrospectionException e) {
logger.severe(() -> "Could not delete config! Error: " + e.getMessage());
logger.severe(() -> "Could not delete object! Error: " + e.getMessage());
}
}
@ -120,4 +132,6 @@ public class Database<T> {
handler.close();
}
}

View File

@ -122,6 +122,28 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
if (!uniqueId.endsWith(JSON)) {
uniqueId = uniqueId + JSON;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, uniqueId);
try {
Files.delete(file.toPath());
return true;
} catch (IOException e) {
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
}
}
return false;
}
@Override
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// Null check
@ -137,25 +159,7 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
// Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance);
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
if (!fileName.endsWith(JSON)) {
fileName = fileName + JSON;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, fileName);
try {
Files.delete(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
}
}
deleteID((String) method.invoke(instance));
}
@Override

View File

@ -106,6 +106,16 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
try {
return collection.findOneAndDelete(new Document(MONGO_ID, uniqueId)) == null ? false : true;
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
return false;
}
}
@Override
public void deleteObject(T instance) {
// Null check
@ -117,11 +127,7 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
try {
collection.findOneAndDelete(new Document(MONGO_ID, ((DataObject)instance).getUniqueId()));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}
deleteID(((DataObject)instance).getUniqueId());
}
@Override

View File

@ -9,6 +9,8 @@ import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Bukkit;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
@ -128,11 +130,18 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
"`" +
dataObject.getCanonicalName() +
"` (json) VALUES (?) ON DUPLICATE KEY UPDATE json = ?";
// Replace into is used so that any data in the table will be replaced with updated data
// The table name is the canonical name, so that add-ons can be sure of a unique table in the database
Gson gson = getGson();
String toStore = gson.toJson(instance);
if (plugin.isEnabled()) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> store(instance, toStore, sb));
} else {
store(instance, toStore, sb);
}
}
private void store(T instance, String toStore, String sb) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
Gson gson = getGson();
String toStore = gson.toJson(instance);
preparedStatement.setString(1, toStore);
preparedStatement.setString(2, toStore);
preparedStatement.execute();
@ -141,6 +150,22 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
}
}
@Override
public boolean deleteID(String uniqueId) {
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
return preparedStatement.getUpdateCount() > 0;
} catch (Exception e) {
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
return false;
}
}
@Override
public void deleteObject(T instance) {
// Null check
@ -152,15 +177,9 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
plugin.logError("This class is not a DataObject: " + instance.getClass().getName());
return;
}
String sb = "DELETE FROM `" +
dataObject.getCanonicalName() +
"` WHERE uniqueId = ?";
try (PreparedStatement preparedStatement = connection.prepareStatement(sb)) {
try {
Method getUniqueId = dataObject.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance);
// UniqueId needs to be placed in quotes
preparedStatement.setString(1, "\"" + uniqueId + "\"");
preparedStatement.execute();
deleteID((String) getUniqueId.invoke(instance));
} catch (Exception e) {
plugin.logError("Could not delete object " + instance.getClass().getName() + " " + e.getMessage());
}

View File

@ -26,11 +26,11 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.FlagSerializer;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Pair;
@ -62,6 +62,10 @@ public class Island implements DataObject {
@Expose
private int protectionRange;
// Maximum ever protection range - used in island deletion
@Expose
private int maxEverProtectionRange;
// World the island started in. This may be different from the island location
@Expose
private World world;
@ -115,6 +119,7 @@ public class Island implements DataObject {
center = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ());
range = BentoBox.getInstance().getIWM().getIslandDistance(world);
this.protectionRange = protectionRange;
this.maxEverProtectionRange = protectionRange;
}
/**
@ -283,6 +288,20 @@ public class Island implements DataObject {
return protectionRange;
}
/**
* @return the maxEverProtectionRange or the protection range, whichever is larger
*/
public int getMaxEverProtectionRange() {
return Math.max(protectionRange, maxEverProtectionRange);
}
/**
* @param maxEverProtectionRange the maxEverProtectionRange to set
*/
public void setMaxEverProtectionRange(int maxEverProtectionRange) {
this.maxEverProtectionRange = maxEverProtectionRange;
}
/**
* @return true if the island is protected from the Purge, otherwise false
*/
@ -532,6 +551,10 @@ public class Island implements DataObject {
*/
public void setProtectionRange(int protectionRange) {
this.protectionRange = protectionRange;
// Ratchet up the maximum protection range
if (protectionRange > this.maxEverProtectionRange) {
this.maxEverProtectionRange = protectionRange;
}
}
/**
@ -659,14 +682,14 @@ public class Island implements DataObject {
// Fixes #getLastPlayed() returning 0 when it is the owner's first connection.
long lastPlayed = (plugin.getServer().getOfflinePlayer(owner).getLastPlayed() != 0) ?
plugin.getServer().getOfflinePlayer(owner).getLastPlayed() : plugin.getServer().getOfflinePlayer(owner).getFirstPlayed();
user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString());
user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString());
user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner)));
String resets = String.valueOf(plugin.getPlayers().getResets(world, owner));
String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world));
user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total);
// Show team members
showMembers(user);
user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner)));
String resets = String.valueOf(plugin.getPlayers().getResets(world, owner));
String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world));
user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total);
// Show team members
showMembers(user);
}
Vector location = center.toVector();
user.sendMessage("commands.admin.info.island-location", "[xyz]", Util.xyz(location));
@ -674,6 +697,7 @@ public class Island implements DataObject {
Vector to = center.toVector().add(new Vector(range-1, 0, range-1)).setY(center.getWorld().getMaxHeight());
user.sendMessage("commands.admin.info.island-coords", "[xz1]", Util.xyz(from), "[xz2]", Util.xyz(to));
user.sendMessage("commands.admin.info.protection-range", "[range]", String.valueOf(protectionRange));
user.sendMessage("commands.admin.info.max-protection-range", "[range]", String.valueOf(maxEverProtectionRange));
Vector pfrom = center.toVector().subtract(new Vector(protectionRange, 0, protectionRange)).setY(0);
Vector pto = center.toVector().add(new Vector(protectionRange-1, 0, protectionRange-1)).setY(center.getWorld().getMaxHeight());
user.sendMessage("commands.admin.info.protection-coords", "[xz1]", Util.xyz(pfrom), "[xz2]", Util.xyz(pto));

View File

@ -0,0 +1,178 @@
package world.bentobox.bentobox.database.objects;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.World;
import com.google.gson.annotations.Expose;
/**
* Data object to store islands in deletion
* @author tastybento
* @since 1.1
*/
public class IslandDeletion implements DataObject {
@Expose
private String uniqueId = "";
@Expose
private Location location;
@Expose
private int minXChunk;
@Expose
private int maxXChunk;
@Expose
private int minZChunk;
@Expose
private int maxZChunk;
public IslandDeletion() {}
public IslandDeletion(Island island) {
uniqueId = UUID.randomUUID().toString();
location = island.getCenter();
minXChunk = (location.getBlockX() - island.getMaxEverProtectionRange()) >> 4;
maxXChunk = (island.getMaxEverProtectionRange() + location.getBlockX() - 1) >> 4;
minZChunk = (location.getBlockZ() - island.getMaxEverProtectionRange()) >> 4;
maxZChunk = (island.getMaxEverProtectionRange() + location.getBlockZ() - 1) >> 4;
}
public IslandDeletion(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) {
this.uniqueId = UUID.randomUUID().toString();
this.location = location;
this.minXChunk = minXChunk;
this.maxXChunk = maxXChunk;
this.minZChunk = minZChunk;
this.maxZChunk = maxZChunk;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof IslandDeletion)) {
return false;
}
IslandDeletion other = (IslandDeletion) obj;
if (uniqueId == null) {
if (other.uniqueId != null) {
return false;
}
} else if (!uniqueId.equals(other.uniqueId)) {
return false;
}
return true;
}
/**
* @return the location
*/
public Location getLocation() {
return location;
}
/**
* @return the maxXChunk
*/
public int getMaxXChunk() {
return maxXChunk;
}
/**
* @return the maxZChunk
*/
public int getMaxZChunk() {
return maxZChunk;
}
/**
* @return the minXChunk
*/
public int getMinXChunk() {
return minXChunk;
}
/**
* @return the minZChunk
*/
public int getMinZChunk() {
return minZChunk;
}
@Override
public String getUniqueId() {
return uniqueId;
}
/**
* @return the world
*/
public World getWorld() {
return location.getWorld();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode());
return result;
}
/**
* @param location the location to set
*/
public void setLocation(Location location) {
this.location = location;
}
/**
* @param maxXChunk the maxXChunk to set
*/
public void setMaxXChunk(int maxXChunk) {
this.maxXChunk = maxXChunk;
}
/**
* @param maxZChunk the maxZChunk to set
*/
public void setMaxZChunk(int maxZChunk) {
this.maxZChunk = maxZChunk;
}
/**
* @param minXChunk the minXChunk to set
*/
public void setMinXChunk(int minXChunk) {
this.minXChunk = minXChunk;
}
/**
* @param minZChunk the minZChunk to set
*/
public void setMinZChunk(int minZChunk) {
this.minZChunk = minZChunk;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
}

View File

@ -6,8 +6,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
@ -18,6 +19,8 @@ import java.util.UUID;
import org.bukkit.configuration.file.YamlConfiguration;
import com.google.common.base.Charsets;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.DatabaseConnector;
@ -78,29 +81,21 @@ public class YamlDatabaseConnector implements DatabaseConnector {
return config;
}
public void saveYamlFile(YamlConfiguration yamlConfig, String tableName, String fileName, Map<String, String> commentMap) {
if (!fileName.endsWith(YML)) {
fileName = fileName + YML;
}
public void saveYamlFile(String data, String tableName, String fileName, Map<String, String> commentMap) {
String name = fileName.endsWith(YML) ? fileName : fileName + YML;
File tableFolder = new File(plugin.getDataFolder(), tableName);
File file = new File(tableFolder, fileName);
File file = new File(tableFolder, name);
if (!tableFolder.exists()) {
tableFolder.mkdirs();
}
try {
File tmpFile = new File(tableFolder, fileName + ".bak");
if (file.exists()) {
// Make a backup of file
Files.copy(file.toPath(), tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
yamlConfig.save(file.toPath().toString());
Files.deleteIfExists(tmpFile.toPath());
} catch (Exception e) {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) {
writer.write(data);
} catch (IOException e) {
plugin.logError("Could not save yml file: " + tableName + " " + fileName + " " + e.getMessage());
return;
}
if (commentMap != null && !commentMap.isEmpty()) {
commentFile(new File(tableFolder, fileName), commentMap);
commentFile(new File(tableFolder, name), commentMap);
}
}

View File

@ -22,6 +22,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.MemorySection;
@ -307,18 +308,14 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
// This is the Yaml Configuration that will be used and saved at the end
YamlConfiguration config = new YamlConfiguration();
// The file name of the Yaml file.
String filename = "";
String path = DATABASE_FOLDER_NAME + File.separator + dataObject.getSimpleName();
// Comments for the file
Map<String, String> yamlComments = new HashMap<>();
// Only allow storing in an arbitrary place if it is a config object. Otherwise it is in the database
StoreAt storeAt = instance.getClass().getAnnotation(StoreAt.class);
if (storeAt != null) {
path = storeAt.path();
filename = storeAt.filename();
}
String path = storeAt == null ? DATABASE_FOLDER_NAME + File.separator + dataObject.getSimpleName() : storeAt.path();
String filename = storeAt == null ? "" : storeAt.filename();
// See if there are any top-level comments
// See if there are multiple comments
ConfigComment.Line comments = instance.getClass().getAnnotation(ConfigComment.Line.class);
@ -443,7 +440,16 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
throw new IllegalArgumentException("No uniqueId in class");
}
((YamlDatabaseConnector)databaseConnector).saveYamlFile(config, path, filename, yamlComments);
// Save
String name = filename;
String data = config.saveToString();
if (plugin.isEnabled()) {
// Async
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments));
} else {
// Sync for shutdown
((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments);
}
}
private void setComment(ConfigComment comment, YamlConfiguration config, Map<String, String> yamlComments, String parent) {
@ -553,6 +559,28 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return value;
}
@Override
public boolean deleteID(String uniqueId) {
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
if (!uniqueId.endsWith(YML)) {
uniqueId = uniqueId + YML;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, uniqueId);
try {
Files.delete(file.toPath());
return true;
} catch (IOException e) {
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
}
}
return false;
}
/* (non-Javadoc)
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteObject(java.lang.Object)
*/
@ -571,23 +599,8 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
// Obtain the value of uniqueId within the instance (which must be a DataObject)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("uniqueId", dataObject);
Method method = propertyDescriptor.getReadMethod();
String fileName = (String) method.invoke(instance);
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
if (!fileName.endsWith(YML)) {
fileName = fileName + YML;
}
// Get the database and table folders
File dataFolder = new File(plugin.getDataFolder(), DATABASE_FOLDER_NAME);
File tableFolder = new File(dataFolder, dataObject.getSimpleName());
if (tableFolder.exists()) {
// Obtain the file and delete it
File file = new File(tableFolder, fileName);
try {
Files.delete(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
}
}
deleteID((String) method.invoke(instance));
}
@Override

View File

@ -0,0 +1,83 @@
package world.bentobox.bentobox.managers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteChunksEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeletedEvent;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.util.DeleteIslandChunks;
import world.bentobox.bentobox.util.Util;
/**
* Listens for island deletions and adds them to the database. Removes them when the island is deleted.
* @author tastybento
* @since 1.1
*/
public class IslandDeletionManager implements Listener {
private BentoBox plugin;
/**
* Queue of islands to delete
*/
private Database<IslandDeletion> handler;
private Set<Location> inDeletion;
public IslandDeletionManager(BentoBox plugin) {
this.plugin = plugin;
handler = new Database<>(plugin, IslandDeletion.class);
inDeletion = new HashSet<>();
}
/**
* When BentoBox is fully loaded, load the islands that still need to be deleted and kick them off
* @param e
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Load list of islands that were mid deletion and delete them
List<IslandDeletion> toBeDeleted = handler.loadObjects();
if (toBeDeleted != null && toBeDeleted.size() > 0) {
plugin.log("There are " + toBeDeleted.size() + " islands pending deletion.");
toBeDeleted.forEach(di -> {
plugin.log("Resuming deletion of island at " + di.getLocation().getWorld().getName() + " " + Util.xyz(di.getLocation().toVector()));
inDeletion.add(di.getLocation());
new DeleteIslandChunks(plugin, di);
});
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onIslandDelete(IslandDeleteChunksEvent e) {
// Store location
inDeletion.add(e.getDeletedIslandInfo().getLocation());
// Save to database
handler.saveObject(e.getDeletedIslandInfo());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onIslandDeleted(IslandDeletedEvent e) {
// Delete
inDeletion.remove(e.getDeletedIslandInfo().getLocation());
// Delete from database
handler.deleteID(e.getDeletedIslandInfo().getUniqueId());
}
/**
* Check if an island location is in deletion
* @param location - center of location
* @return true if island is in the process of being deleted
*/
public boolean inDeletion(Location location) {
return inDeletion.contains(location);
}
}

View File

@ -24,9 +24,13 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.objects.IslandDeletion;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
import world.bentobox.bentobox.managers.island.IslandCache;
@ -60,9 +64,6 @@ public class IslandsManager {
// Island Cache
private IslandCache islandCache;
// Async database saving semaphore
private boolean midSave;
/**
* Islands Manager
* @param plugin - plugin
@ -244,6 +245,11 @@ public class IslandsManager {
if (island == null) {
return;
}
// Fire event
IslandBaseEvent event = IslandEvent.builder().island(island).reason(Reason.DELETE).build();
if (event.isCancelled()) {
return;
}
// Set the owner of the island to no one.
island.setOwner(null);
island.setFlag(Flags.LOCK, RanksManager.VISITOR_RANK);
@ -255,7 +261,7 @@ public class IslandsManager {
// Remove players from island
removePlayersFromIsland(island);
// Remove blocks from world
new DeleteIslandChunks(plugin, island);
new DeleteIslandChunks(plugin, new IslandDeletion(island));
}
}
@ -685,8 +691,7 @@ public class IslandsManager {
* @param user - user
*/
public void removePlayer(World world, User user) {
islandCache.removePlayer(world, user.getUniqueId());
save(true);
removePlayer(world, user.getUniqueId());
}
/**
@ -695,8 +700,10 @@ public class IslandsManager {
* @param uuid - user's uuid
*/
public void removePlayer(World world, UUID uuid) {
islandCache.removePlayer(world, uuid);
save(true);
Island island = islandCache.removePlayer(world, uuid);
if (island != null) {
handler.saveObject(island);
}
}
/**
@ -727,36 +734,18 @@ public class IslandsManager {
}
/**
* Save the islands to the database
* @param async - if true, saving will be done async
* Save the all the islands to the database
*/
public void save(boolean async){
if (midSave) {
// If it's already saving, then do nothing
return;
}
public void saveAll(){
Collection<Island> collection = islandCache.getIslands();
if(async) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
midSave = true;
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running async! " + e.getMessage());
}
}
midSave = false;
});
} else {
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
for(Island island : collection){
try {
handler.saveObject(island);
} catch (Exception e) {
plugin.logError("Could not save island to database when running sync! " + e.getMessage());
}
}
}
/**
@ -768,8 +757,8 @@ public class IslandsManager {
// Add player to new island
teamIsland.addMember(playerUUID);
islandCache.addPlayer(playerUUID, teamIsland);
// Save the database
save(false);
// Save the island
handler.saveObject(teamIsland);
}
public void setLast(Location last) {
@ -789,7 +778,7 @@ public class IslandsManager {
public void shutdown(){
// Remove all coop associations
islandCache.getIslands().stream().forEach(i -> i.getMembers().values().removeIf(p -> p == RanksManager.COOP_RANK));
save(false);
saveAll();
islandCache.clear();
handler.close();
}
@ -867,4 +856,13 @@ public class IslandsManager {
islandCache.getIslands().stream().forEach(i -> i.getMembers().entrySet().removeIf(e -> e.getKey().equals(uniqueId) && e.getValue() == rank));
}
/**
* Save the island to the database
* @param island - island
*/
public void save(Island island) {
handler.saveObject(island);
}
}

View File

@ -1,6 +1,5 @@
package world.bentobox.bentobox.managers;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -8,7 +7,6 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
@ -63,20 +61,13 @@ public class PlayersManager {
/**
* Save all players
* @param async - if true, save async
*/
public void save(boolean async){
Collection<Players> set = Collections.unmodifiableCollection(playerCache.values());
if(async) {
Runnable save = () -> set.forEach(handler::saveObject);
Bukkit.getScheduler().runTaskAsynchronously(plugin, save);
} else {
set.forEach(handler::saveObject);
}
public void saveAll(){
Collections.unmodifiableCollection(playerCache.values()).forEach(handler::saveObject);
}
public void shutdown(){
save(false);
saveAll();
playerCache.clear();
handler.close();
}

View File

@ -169,8 +169,9 @@ public class IslandCache {
* The island is removed from the islandsByUUID map, but kept in the location map.
* @param world - world
* @param uuid - player's UUID
* @return island player had or null if none
*/
public void removePlayer(World world, UUID uuid) {
public Island removePlayer(World world, UUID uuid) {
world = Util.getWorld(world);
islandsByUUID.putIfAbsent(world, new HashMap<>());
Island island = islandsByUUID.get(world).get(uuid);
@ -185,6 +186,7 @@ public class IslandCache {
}
}
islandsByUUID.get(world).remove(uuid);
return island;
}
/**

View File

@ -244,7 +244,7 @@ public class NewIsland {
*/
private Result isIsland(Location location){
location = Util.getClosestIsland(location);
if (plugin.getIslands().getIslandAt(location).isPresent()) {
if (plugin.getIslands().getIslandAt(location).isPresent() || plugin.getIslandDeletionManager().inDeletion(location)) {
return Result.ISLAND_FOUND;
}

View File

@ -353,8 +353,8 @@ public class Clipboard {
/**
* Sets any entity that is in this location
* @param location - locaton
* @param config - config section
* @param location - location
* @param en - config section
*/
private void setEntity(Location location, ConfigurationSection en) {
en.getKeys(false).forEach(k -> {

View File

@ -1,12 +1,12 @@
package world.bentobox.bentobox.util;
import org.bukkit.World;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.database.objects.IslandDeletion;
/**
* Deletes islands fast using chunk regeneration
@ -17,40 +17,41 @@ import world.bentobox.bentobox.database.objects.Island;
public class DeleteIslandChunks {
/**
* Deletes the island
* @param plugin - plugin object
* @param island - island to delete
* This is how many chunks per world will be done in one tick.
*/
private final static int SPEED = 5;
private int x;
private int z;
private BukkitTask task;
@SuppressWarnings("deprecation")
public DeleteIslandChunks(final BentoBox plugin, final Island island) {
public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) {
// Fire event
IslandBaseEvent event = IslandEvent.builder().island(island).reason(Reason.DELETE).build();
if (event.isCancelled()) {
return;
}
final World world = island.getCenter().getWorld();
if (world == null) {
return;
}
int minXChunk = island.getMinX() >> 4;
int maxXChunk = (island.getRange() * 2 + island.getMinX() - 1) >> 4;
int minZChunk = island.getMinZ() >> 4;
int maxZChunk = (island.getRange() * 2 + island.getMinZ() - 1) >> 4;
for (int x = minXChunk; x <= maxXChunk; x++) {
for (int z = minZChunk; z<=maxZChunk; z++) {
world.regenerateChunk(x, z);
//System.out.println("regenerating = " + x + "," + z);
if (plugin.getIWM().isNetherGenerate(world) && plugin.getIWM().isNetherIslands(world)) {
plugin.getIWM().getNetherWorld(world).regenerateChunk(x, z);
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETE_CHUNKS).build();
x = di.getMinXChunk();
z = di.getMinZChunk();
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (int i = 0; i < SPEED; i++) {
di.getWorld().regenerateChunk(x, z);
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z);
}
if (plugin.getIWM().isEndGenerate(world) && plugin.getIWM().isEndIslands(world)) {
plugin.getIWM().getEndWorld(world).regenerateChunk(x, z);
if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) {
plugin.getIWM().getEndWorld(di.getWorld()).regenerateChunk(x, z);
}
z++;
if (z > di.getMaxZChunk()) {
z = di.getMinZChunk();
x++;
if (x > di.getMaxXChunk()) {
task.cancel();
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
}
}
}
}
// Fire event
IslandEvent.builder().island(island).reason(Reason.DELETED).build();
}, 0L, 1L);
}

View File

@ -109,6 +109,7 @@ commands:
registered-island: "&aRegistered player to island at [xyz]."
already-owned: "&cIsland is already owned by another player!"
no-island-here: "&cThere is no island here. Confirm to make one."
in-deletion: "&cThis island space is currently being deleted. Try later."
unregister:
parameters: "<owner>"
description: "unregister owner from island, but keep island blocks"
@ -128,6 +129,7 @@ commands:
island-location: "Island location: [xyz]"
island-coords: "Island coordinates: [xz1] to [xz2]"
protection-range: "Protection range: [range]"
max-protection-range: "Largest historical protection range: [range]"
protection-coords: "Protection coordinates: [xz1] to [xz2]"
is-spawn: "Island is a spawn island"
banned-players: "Banned players:"

View File

@ -30,6 +30,7 @@ import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandDeletionManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
@ -50,6 +51,7 @@ public class AdminRegisterCommandTest {
private IslandsManager im;
private PlayersManager pm;
private UUID notUUID;
private IslandDeletionManager idm;
/**
* @throws java.lang.Exception
@ -111,11 +113,16 @@ public class AdminRegisterCommandTest {
LocalesManager lm = mock(LocalesManager.class);
when(lm.get(Mockito.any(), Mockito.any())).thenReturn("mock translation");
when(plugin.getLocalesManager()).thenReturn(lm);
// Deletion Manager
idm = mock(IslandDeletionManager.class);
when(idm.inDeletion(Mockito.any())).thenReturn(false);
when(plugin.getIslandDeletionManager()).thenReturn(idm);
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteNoTarget() {
@ -125,7 +132,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteUnknownPlayer() {
@ -137,7 +144,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecutePlayerHasIsland() {
@ -151,7 +158,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteInTeam() {
@ -165,7 +172,7 @@ public class AdminRegisterCommandTest {
}
/**
* Test method for .
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteAlreadyOwnedIsland() {
@ -186,6 +193,32 @@ public class AdminRegisterCommandTest {
Mockito.verify(user).sendMessage("commands.admin.register.already-owned");
}
/**
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteInDeletionIsland() {
when(idm.inDeletion(Mockito.any())).thenReturn(true);
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(false);
when(im.hasIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(false);
String[] name = {"tastybento"};
when(pm.getUUID(Mockito.any())).thenReturn(notUUID);
Location loc = mock(Location.class);
// Island has owner
Island is = mock(Island.class);
when(is.getOwner()).thenReturn(uuid);
Optional<Island> opi = Optional.of(is);
when(im.getIslandAt(Mockito.any())).thenReturn(opi);
when(user.getLocation()).thenReturn(loc);
AdminRegisterCommand itl = new AdminRegisterCommand(ac);
assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
Mockito.verify(user).sendMessage("commands.admin.register.in-deletion");
}
/**
* Test method for {@link AdminRegisterCommand#execute(org.bukkit.command.CommandSender, String, String[])}.
*/
@Test
public void testExecuteSuccess() {
when(im.inTeam(Mockito.any(), Mockito.any())).thenReturn(false);

View File

@ -23,6 +23,7 @@ import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@ -37,6 +38,7 @@ import org.bukkit.entity.Player;
import org.bukkit.entity.Slime;
import org.bukkit.entity.Wither;
import org.bukkit.entity.Zombie;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.junit.Before;
import org.junit.Test;
@ -54,6 +56,7 @@ import com.google.common.collect.ImmutableSet.Builder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.events.island.IslandEvent.IslandDeleteEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.lists.Flags;
@ -79,6 +82,7 @@ public class IslandsManagerTest {
private IslandCache islandCache;
private Optional<Island> optionalIsland;
private Island is;
private PluginManager pim;
/**
* @throws java.lang.Exception
@ -122,8 +126,7 @@ public class IslandsManagerTest {
pm = mock(PlayersManager.class);
when(plugin.getPlayers()).thenReturn(pm);
// Server & Scheduler
// Scheduler
BukkitScheduler sch = mock(BukkitScheduler.class);
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch);
@ -174,6 +177,11 @@ public class IslandsManagerTest {
// User location
when(user.getLocation()).thenReturn(location);
// Server for events
Server server = mock(Server.class);
when(Bukkit.getServer()).thenReturn(server);
pim = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(pim);
}
@ -432,16 +440,37 @@ public class IslandsManagerTest {
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBoolean() {
public void testDeleteIslandIslandBooleanNull() {
IslandsManager im = new IslandsManager(plugin);
im.deleteIsland((Island)null, true);
Mockito.verify(pim, Mockito.never()).callEvent(Mockito.any());
}
/**
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBooleanNoBlockRemoval() {
IslandsManager im = new IslandsManager(plugin);
UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner);
im.deleteIsland(island, false);
island = im.createIsland(location, owner);
assertNull(island.getOwner());
Mockito.verify(pim, Mockito.times(2)).callEvent(Mockito.any(IslandDeleteEvent.class));
}
/**
* Test method for {@link world.bentobox.bentobox.managers.IslandsManager#deleteIsland(world.bentobox.bentobox.database.objects.Island, boolean)}.
*/
@Test
public void testDeleteIslandIslandBooleanRemoveBlocks() {
Mockito.verify(pim, Mockito.never()).callEvent(Mockito.any());
IslandsManager im = new IslandsManager(plugin);
UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner);
im.deleteIsland(island, true);
assertNull(island);
assertNull(island.getOwner());
Mockito.verify(pim, Mockito.times(4)).callEvent(Mockito.any(IslandDeleteEvent.class));
}
/**

View File

@ -175,13 +175,12 @@ public class PlayersManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#save(boolean)}.
* Test method for {@link world.bentobox.bentobox.managers.PlayersManager#saveAll()}.
*/
@Test
public void testSaveBoolean() {
PlayersManager pm = new PlayersManager(plugin);
pm.save(false);
pm.save(true);
pm.saveAll();
}
/**

View File

@ -1,152 +0,0 @@
/**
*
*/
package world.bentobox.bentobox.util;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.plugin.PluginManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandWorldManager;
/**
* Tests the island delete class
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({Bukkit.class})
public class DeleteIslandChunksTest {
private BentoBox plugin;
private Island island;
private Location location;
private World world;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
PowerMockito.mockStatic(Bukkit.class);
Server server = mock(Server.class);
PluginManager pim = mock(PluginManager.class);
when(server.getPluginManager()).thenReturn(pim);
when(Bukkit.getServer()).thenReturn(server);
plugin = mock(BentoBox.class);
IslandWorldManager iwm = mock(IslandWorldManager.class);
// No Nether or End by default
when(iwm.isNetherGenerate(Mockito.any())).thenReturn(false);
when(iwm.isNetherIslands(Mockito.any())).thenReturn(false);
when(iwm.isEndGenerate(Mockito.any())).thenReturn(false);
when(iwm.isEndIslands(Mockito.any())).thenReturn(false);
when(plugin.getIWM()).thenReturn(iwm);
// World
//world = mock(World.class, Mockito.withSettings().verboseLogging());
world = mock(World.class);
island = new Island();
island.setRange(64);
location = mock(Location.class);
when(location.getWorld()).thenReturn(world);
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksNegativeX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(-128);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksNegativeXX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(-256);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksIslandPositiveX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(0);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
/**
* Test method for {@link world.bentobox.bentobox.util.DeleteIslandChunks#DeleteIslandChunks(world.bentobox.bentobox.BentoBox, world.bentobox.bentobox.database.objects.Island)}.
*/
@SuppressWarnings("deprecation")
@Test
public void testDeleteIslandChunksPositiveXX() {
// Island adjacent to an island at 0,0
Location location2 = mock(Location.class);
when(location2.getWorld()).thenReturn(world);
when(location2.getBlockX()).thenReturn(256);
when(location2.getBlockY()).thenReturn(120);
when(location2.getBlockZ()).thenReturn(0);
island.setCenter(location2);
new DeleteIslandChunks(plugin, island);
Mockito.verify(world, Mockito.times(64)).regenerateChunk(Mockito.anyInt(), Mockito.anyInt());
}
}