diff --git a/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java index 303e00779..7d29ac5ef 100644 --- a/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/AbstractDatabaseHandler.java @@ -120,8 +120,7 @@ public abstract class AbstractDatabaseHandler { /** * 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); + public abstract void deleteID(String uniqueId); } diff --git a/src/main/java/world/bentobox/bentobox/database/Database.java b/src/main/java/world/bentobox/bentobox/database/Database.java index 4ae50ac2c..cbfae24fe 100644 --- a/src/main/java/world/bentobox/bentobox/database/Database.java +++ b/src/main/java/world/bentobox/bentobox/database/Database.java @@ -79,9 +79,9 @@ public class Database { } /** - * Save config object + * Save config object. Saving may be done async. * @param instance to save - * @return true if successfully saved + * @return true if no immediate errors. If async, errors may occur later. */ public boolean saveObject(T instance) { try { @@ -106,11 +106,10 @@ public class Database { /** * 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); + public void deleteID(String uniqueId) { + handler.deleteID(uniqueId); } /** diff --git a/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java index 09ae57765..7e5e1391a 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/json/JSONDatabaseHandler.java @@ -127,7 +127,7 @@ public class JSONDatabaseHandler extends AbstractJSONDatabaseHandler { } @Override - public boolean deleteID(String uniqueId) { + public void 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; @@ -140,12 +140,10 @@ public class JSONDatabaseHandler extends AbstractJSONDatabaseHandler { 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 diff --git a/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java index 80fb98c9a..dd6cfd5e0 100644 --- a/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mariadb/MariaDBDatabaseHandler.java @@ -158,7 +158,7 @@ public class MariaDBDatabaseHandler extends AbstractJSONDatabaseHandler { } @Override - public boolean deleteID(String uniqueId) { + public void deleteID(String uniqueId) { String sb = "DELETE FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ?"; @@ -166,10 +166,8 @@ public class MariaDBDatabaseHandler extends AbstractJSONDatabaseHandler { // 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; } } diff --git a/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java index 7367143f0..f619f2a14 100644 --- a/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mongodb/MongoDBDatabaseHandler.java @@ -117,12 +117,11 @@ public class MongoDBDatabaseHandler extends AbstractJSONDatabaseHandler { } @Override - public boolean deleteID(String uniqueId) { + public void deleteID(String uniqueId) { try { - return collection.findOneAndDelete(new Document(MONGO_ID, uniqueId)) != null; + collection.findOneAndDelete(new Document(MONGO_ID, uniqueId)); } catch (Exception e) { plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage()); - return false; } } diff --git a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java index 838ca2e0b..34514bbe2 100644 --- a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java @@ -42,12 +42,12 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { private Connection connection; /** - * FIFO queue for saves. Note that the assumption here is that most database objects will be held + * FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held * in memory because loading is not handled with this queue. That means that it is theoretically * possible to load something before it has been saved. So, in general, load your objects and then * save them async only when you do not need the data again immediately. */ - private Queue saveQueue; + private Queue processQueue; /** * Async save task that runs repeatedly @@ -72,13 +72,13 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { } // Check if the table exists in the database and if not, create it createSchema(); - saveQueue = new ConcurrentLinkedQueue<>(); + processQueue = new ConcurrentLinkedQueue<>(); if (plugin.isEnabled()) { asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { // Loop continuously - while (plugin.isEnabled() || !saveQueue.isEmpty()) { - while (!saveQueue.isEmpty()) { - saveQueue.poll().run(); + while (plugin.isEnabled() || !processQueue.isEmpty()) { + while (!processQueue.isEmpty()) { + processQueue.poll().run(); } // Clear the queue and then sleep try { @@ -189,7 +189,7 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { String toStore = gson.toJson(instance); if (plugin.isEnabled()) { // Async - saveQueue.add(() -> store(instance, toStore, sb)); + processQueue.add(() -> store(instance, toStore, sb)); } else { // Sync store(instance, toStore, sb); @@ -206,8 +206,19 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { } } + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteID(java.lang.String) + */ @Override - public boolean deleteID(String uniqueId) { + public void deleteID(String uniqueId) { + if (plugin.isEnabled()) { + processQueue.add(() -> delete(uniqueId)); + } else { + delete(uniqueId); + } + } + + private void delete(String uniqueId) { String sb = "DELETE FROM `" + dataObject.getCanonicalName() + "` WHERE uniqueId = ?"; @@ -215,10 +226,8 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { // 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; } } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 0c1f9c81a..ce8264f4e 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -44,11 +44,15 @@ import world.bentobox.bentobox.util.Util; * Managed by IslandsManager * Responsible for team information as well. * - * @author Tastybento + * @author tastybento * @author Poslovitch */ public class Island implements DataObject { + // True if this island is deleted and pending deletion from the database + @Expose + private boolean deleted = false; + @Expose private String uniqueId = UUID.randomUUID().toString(); @@ -850,4 +854,18 @@ public class Island implements DataObject { public void setDoNotLoad(boolean doNotLoad) { this.doNotLoad = doNotLoad; } + + /** + * @return the deleted + */ + public boolean isDeleted() { + return deleted; + } + + /** + * @param deleted the deleted to set + */ + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java index ca48ad1ff..c33a0ea41 100644 --- a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java @@ -65,7 +65,7 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { * possible to load something before it has been saved. So, in general, load your objects and then * save them async only when you do not need the data again immediately. */ - private Queue saveQueue; + private Queue processQueue; /** * Async save task that runs repeatedly @@ -87,13 +87,13 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { */ YamlDatabaseHandler(BentoBox plugin, Class type, DatabaseConnector databaseConnector) { super(plugin, type, databaseConnector); - saveQueue = new ConcurrentLinkedQueue<>(); + processQueue = new ConcurrentLinkedQueue<>(); if (plugin.isEnabled()) { asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { // Loop continuously - while (plugin.isEnabled() || !saveQueue.isEmpty()) { - while (!saveQueue.isEmpty()) { - saveQueue.poll().run(); + while (plugin.isEnabled() || !processQueue.isEmpty()) { + while (!processQueue.isEmpty()) { + processQueue.poll().run(); } // Clear the queue and then sleep try { @@ -439,7 +439,7 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { private void save(String name, String data, String path, Map yamlComments) { if (plugin.isEnabled()) { // Async - saveQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments)); + processQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments)); } else { // Sync for shutdown ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments); @@ -640,7 +640,15 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { } @Override - public boolean deleteID(String uniqueId) { + public void deleteID(String uniqueId) { + if (plugin.isEnabled()) { + processQueue.add(() -> delete(uniqueId)); + } else { + delete(uniqueId); + } + } + + private void delete(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; @@ -653,12 +661,10 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { 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) @@ -668,7 +674,7 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException { // Null check if (instance == null) { - plugin.logError("YAM database request to delete a null. "); + plugin.logError("YAML database request to delete a null. "); return; } if (!(instance instanceof DataObject)) { diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 43d103be5..910bcc859 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -34,6 +34,7 @@ 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.logs.LogEntry; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.objects.Island; @@ -72,8 +73,12 @@ public class IslandsManager { // Island Cache @NonNull private IslandCache islandCache; + // Quarantined islands @NonNull private Map> quarantineCache; + // Deleted islands + @NonNull + private List deletedIslands; /** * Islands Manager @@ -87,6 +92,9 @@ public class IslandsManager { quarantineCache = new HashMap<>(); spawn = new HashMap<>(); last = new HashMap<>(); + // This list should always be empty unless database deletion failed + // In that case a purge utility may be required in the future + deletedIslands = new ArrayList<>(); } /** @@ -266,7 +274,13 @@ public class IslandsManager { if (removeBlocks) { // Remove island from the cache islandCache.deleteIslandFromCache(island); - // Remove the island from the database + // Log the deletion (it shouldn't matter but may be useful) + island.log(new LogEntry.Builder("DELETED").build()); + // Set the delete flag which will prevent it from being loaded even if database deletion fails + island.setDeleted(true); + // Save the island + handler.saveObject(island); + // Delete the island handler.deleteObject(island); // Remove players from island removePlayersFromIsland(island); @@ -703,9 +717,12 @@ public class IslandsManager { islandCache.clear(); quarantineCache.clear(); List toQuarantine = new ArrayList<>(); - // Only load non-quarantined island + // Attempt to load islands handler.loadObjects().stream().forEach(island -> { - if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) { + if (island.isDeleted()) { + // These will be deleted later + deletedIslands.add(island.getUniqueId()); + } else if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) { // Add to quarantine cache quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island); } else { @@ -809,8 +826,7 @@ public class IslandsManager { } /** - * This removes players from an island overworld and nether - used when reseting or deleting an island - * Mobs are killed when the chunks are refreshed. + * This teleports players away from an island - used when reseting or deleting an island * @param island to remove players from */ public void removePlayersFromIsland(Island island) { diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index 29b021fa0..dc8ae58a7 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -281,7 +281,7 @@ public class IslandCache { grids.get(Util.getWorld(island.getWorld())).removeFromGrid(island); } } - + /** * Resets all islands in this game mode to default flag settings * @param world - world