From 7b3dac61acd672233e2bb0b3779d8c4349280636 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 8 Feb 2017 17:02:03 +0100 Subject: [PATCH] Query and update SQL database asynchronously --- .../java/de/epiceric/shopchest/ShopChest.java | 66 ++- .../de/epiceric/shopchest/ShopCommand.java | 16 +- .../exceptions/ChestNotFoundException.java | 9 + .../exceptions/NotEnoughSpaceException.java | 9 + .../exceptions/WorldNotFoundException.java | 9 + .../listeners/ChestProtectListener.java | 12 +- .../listeners/ShopInteractListener.java | 82 +-- .../de/epiceric/shopchest/nms/Hologram.java | 51 +- .../java/de/epiceric/shopchest/shop/Shop.java | 65 ++- .../de/epiceric/shopchest/sql/Database.java | 466 ++++++++++-------- .../java/de/epiceric/shopchest/sql/MySQL.java | 22 +- .../de/epiceric/shopchest/utils/Callback.java | 34 ++ .../epiceric/shopchest/utils/ShopUtils.java | 79 ++- 13 files changed, 588 insertions(+), 332 deletions(-) create mode 100644 src/main/java/de/epiceric/shopchest/exceptions/ChestNotFoundException.java create mode 100644 src/main/java/de/epiceric/shopchest/exceptions/NotEnoughSpaceException.java create mode 100644 src/main/java/de/epiceric/shopchest/exceptions/WorldNotFoundException.java create mode 100644 src/main/java/de/epiceric/shopchest/utils/Callback.java diff --git a/src/main/java/de/epiceric/shopchest/ShopChest.java b/src/main/java/de/epiceric/shopchest/ShopChest.java index af2dc93..9422fc2 100644 --- a/src/main/java/de/epiceric/shopchest/ShopChest.java +++ b/src/main/java/de/epiceric/shopchest/ShopChest.java @@ -226,7 +226,7 @@ public class ShopChest extends JavaPlugin { ShopReloadEvent event = new ShopReloadEvent(Bukkit.getConsoleSender()); Bukkit.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) shopUtils.reloadShops(true, false); + if (!event.isCancelled()) shopUtils.reloadShops(true, false, null); } }, config.auto_reload_time * 20, config.auto_reload_time * 20); } @@ -288,17 +288,17 @@ public class ShopChest extends JavaPlugin { @Override public void onDisable() { - debug("Disabling ShopChest..."); + debug("Disabling ShopChest...", true); if (updater != null) { - debug("Stopping updater"); + debug("Stopping updater", true); updater.cancel(); } if (database != null) { for (Shop shop : shopUtils.getShops()) { - shopUtils.removeShop(shop, false); - debug("Removed shop (#" + shop.getID() + ")"); + shopUtils.removeShop(shop, false, true); + debug("Removed shop (#" + shop.getID() + ")", true); } database.disconnect(); @@ -314,6 +314,26 @@ public class ShopChest extends JavaPlugin { } } + /** + * Print a message to the /plugins/ShopChest/debug.txt file + * @param message Message to print + * @param useCurrentThread Whether the current thread should be used. If set to false, a new synchronized task will be created. + */ + public void debug(final String message, boolean useCurrentThread) { + if (config.enable_debug_log && fw != null) { + if (useCurrentThread) { + debug(message); + } else { + new BukkitRunnable() { + @Override + public void run() { + debug(message); + } + }.runTask(this); + } + } + } + /** * Print a message to the /plugins/ShopChest/debug.txt file * @param message Message to print @@ -332,6 +352,28 @@ public class ShopChest extends JavaPlugin { } } + /** + * Print a {@link Throwable}'s stacktrace to the /plugins/ShopChest/debug.txt file + * @param throwable {@link Throwable} whose stacktrace will be printed + * @param useCurrentThread Whether the current thread should be used. If set to false, a new synchronized task will be created. + */ + public void debug(final Throwable throwable, boolean useCurrentThread) { + if (config.enable_debug_log && fw != null) { + if (useCurrentThread) { + debug(throwable); + } else { + new BukkitRunnable() { + @Override + public void run() { + PrintWriter pw = new PrintWriter(fw); + throwable.printStackTrace(pw); + pw.flush(); + } + }.runTask(this); + } + } + } + /** * Print a {@link Throwable}'s stacktrace to the /plugins/ShopChest/debug.txt file * @param throwable {@link Throwable} whose stacktrace will be printed @@ -350,9 +392,17 @@ public class ShopChest extends JavaPlugin { */ private void initializeShops() { debug("Initializing Shops..."); - int count = shopUtils.reloadShops(false, false); - getLogger().info("Initialized " + count + " Shops"); - debug("Initialized " + count + " Shops"); + shopUtils.reloadShops(false, false, new Callback(this) { + @Override + public void onResult(Object result) { + if (result instanceof Integer) { + int count = (int) result; + getLogger().info("Initialized " + count + " Shops"); + debug("Initialized " + count + " Shops"); + } + } + }); + } /** diff --git a/src/main/java/de/epiceric/shopchest/ShopCommand.java b/src/main/java/de/epiceric/shopchest/ShopCommand.java index eef4593..5b8ab36 100644 --- a/src/main/java/de/epiceric/shopchest/ShopCommand.java +++ b/src/main/java/de/epiceric/shopchest/ShopCommand.java @@ -241,7 +241,7 @@ class ShopCommand implements CommandExecutor { * A given player reloads the shops * @param sender The command executor */ - private void reload(CommandSender sender) { + private void reload(final CommandSender sender) { plugin.debug(sender.getName() + " is reloading the shops"); ShopReloadEvent event = new ShopReloadEvent(sender); @@ -251,9 +251,17 @@ class ShopCommand implements CommandExecutor { return; } - int count = shopUtils.reloadShops(true, true); - plugin.debug(sender.getName() + " has reloaded " + count + " shops"); - sender.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.RELOADED_SHOPS, new LocalizedMessage.ReplacedRegex(Regex.AMOUNT, String.valueOf(count)))); + shopUtils.reloadShops(true, true, new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Integer) { + int count = (int) result; + sender.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.RELOADED_SHOPS, new LocalizedMessage.ReplacedRegex(Regex.AMOUNT, String.valueOf(count)))); + plugin.debug(sender.getName() + " has reloaded " + count + " shops"); + } + } + }); + } /** diff --git a/src/main/java/de/epiceric/shopchest/exceptions/ChestNotFoundException.java b/src/main/java/de/epiceric/shopchest/exceptions/ChestNotFoundException.java new file mode 100644 index 0000000..1b6ed4c --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/exceptions/ChestNotFoundException.java @@ -0,0 +1,9 @@ +package de.epiceric.shopchest.exceptions; + +public class ChestNotFoundException extends Exception { + + public ChestNotFoundException(String message) { + super(message); + } + +} diff --git a/src/main/java/de/epiceric/shopchest/exceptions/NotEnoughSpaceException.java b/src/main/java/de/epiceric/shopchest/exceptions/NotEnoughSpaceException.java new file mode 100644 index 0000000..6c82e0b --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/exceptions/NotEnoughSpaceException.java @@ -0,0 +1,9 @@ +package de.epiceric.shopchest.exceptions; + +public class NotEnoughSpaceException extends Exception { + + public NotEnoughSpaceException(String message) { + super(message); + } + +} diff --git a/src/main/java/de/epiceric/shopchest/exceptions/WorldNotFoundException.java b/src/main/java/de/epiceric/shopchest/exceptions/WorldNotFoundException.java new file mode 100644 index 0000000..57ec094 --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/exceptions/WorldNotFoundException.java @@ -0,0 +1,9 @@ +package de.epiceric.shopchest.exceptions; + +public class WorldNotFoundException extends Exception { + + public WorldNotFoundException(String message) { + super(message); + } + +} diff --git a/src/main/java/de/epiceric/shopchest/listeners/ChestProtectListener.java b/src/main/java/de/epiceric/shopchest/listeners/ChestProtectListener.java index a6cec8c..2989db8 100644 --- a/src/main/java/de/epiceric/shopchest/listeners/ChestProtectListener.java +++ b/src/main/java/de/epiceric/shopchest/listeners/ChestProtectListener.java @@ -16,6 +16,7 @@ import de.epiceric.shopchest.utils.ShopUtils; import de.epiceric.shopchest.utils.Utils; import de.epiceric.shopchest.worldguard.ShopFlag; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -62,13 +63,9 @@ public class ChestProtectListener implements Listener { Bukkit.getScheduler().runTaskLater(plugin, new Runnable() { @Override public void run() { - Shop newShop = null; - - if (b.getLocation().equals(l.getLocation())) - newShop = new Shop(shop.getID(), plugin, shop.getVendor(), shop.getProduct(), r.getLocation(), shop.getBuyPrice(), shop.getSellPrice(), shop.getShopType()); - else if (b.getLocation().equals(r.getLocation())) - newShop = new Shop(shop.getID(), plugin, shop.getVendor(), shop.getProduct(), l.getLocation(), shop.getBuyPrice(), shop.getSellPrice(), shop.getShopType()); - + Location loc = (b.getLocation().equals(l.getLocation()) ? r.getLocation() : l.getLocation()); + Shop newShop = new Shop(shop.getID(), plugin, shop.getVendor(), shop.getProduct(), loc, shop.getBuyPrice(), shop.getSellPrice(), shop.getShopType()); + newShop.create(); shopUtils.addShop(newShop, true); } }, 1L); @@ -170,6 +167,7 @@ public class ChestProtectListener implements Listener { if (b.getRelative(BlockFace.UP).getType() == Material.AIR) { shopUtils.removeShop(shop, true); Shop newShop = new Shop(shop.getID(), ShopChest.getInstance(), shop.getVendor(), shop.getProduct(), shop.getLocation(), shop.getBuyPrice(), shop.getSellPrice(), shop.getShopType()); + newShop.create(); shopUtils.addShop(newShop, true); plugin.debug(String.format("%s extended %s's shop (#%d)", p.getName(), shop.getVendor().getName(), shop.getID())); } else { diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java index 110c28a..5630393 100644 --- a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java +++ b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java @@ -17,10 +17,7 @@ import de.epiceric.shopchest.nms.Hologram; import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.shop.Shop.ShopType; import de.epiceric.shopchest.sql.Database; -import de.epiceric.shopchest.utils.ClickType; -import de.epiceric.shopchest.utils.Permissions; -import de.epiceric.shopchest.utils.ShopUtils; -import de.epiceric.shopchest.utils.Utils; +import de.epiceric.shopchest.utils.*; import de.epiceric.shopchest.worldguard.ShopFlag; import fr.xephi.authme.AuthMe; import net.milkbowl.vault.economy.Economy; @@ -447,45 +444,56 @@ public class ShopInteractListener implements Listener { * @param sellPrice Sell price * @param shopType Type of the shop */ - private void create(Player executor, Location location, ItemStack product, double buyPrice, double sellPrice, ShopType shopType) { + private void create(final Player executor, final Location location, final ItemStack product, final double buyPrice, final double sellPrice, final ShopType shopType) { plugin.debug(executor.getName() + " is creating new shop..."); - int id = database.getNextFreeID(); - double creationPrice = (shopType == ShopType.NORMAL) ? config.shop_creation_price_normal : config.shop_creation_price_admin; + database.getNextFreeID(new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Integer) { + int id = (int) result; + double creationPrice = (shopType == ShopType.NORMAL) ? config.shop_creation_price_normal : config.shop_creation_price_admin; + Shop shop = new Shop(id, plugin, executor, product, location, buyPrice, sellPrice, shopType); - ShopCreateEvent event = new ShopCreateEvent(executor, Shop.createImaginaryShop(executor, product, location, buyPrice, sellPrice,shopType), creationPrice); - Bukkit.getPluginManager().callEvent(event); + ShopCreateEvent event = new ShopCreateEvent(executor, shop, creationPrice); + Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - plugin.debug("Create event cancelled (#" + id + ")"); - return; - } - EconomyResponse r = plugin.getEconomy().withdrawPlayer(executor, location.getWorld().getName(), creationPrice); - if (!r.transactionSuccess()) { - plugin.debug("Economy transaction failed: " + r.errorMessage); - executor.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.ERROR_OCCURRED, new LocalizedMessage.ReplacedRegex(Regex.ERROR, r.errorMessage))); - return; - } + if (event.isCancelled()) { + plugin.debug("Create event cancelled (#" + id + ")"); + return; + } - Shop shop = new Shop(id, plugin, executor, product, location, buyPrice, sellPrice, shopType); + EconomyResponse r = plugin.getEconomy().withdrawPlayer(executor, location.getWorld().getName(), creationPrice); + if (!r.transactionSuccess()) { + plugin.debug("Economy transaction failed: " + r.errorMessage); + executor.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.ERROR_OCCURRED, new LocalizedMessage.ReplacedRegex(Regex.ERROR, r.errorMessage))); + return; + } - plugin.debug("Shop created (#" + id + ")"); - shopUtils.addShop(shop, true); - executor.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_CREATED)); + shop.create(); - for (Player p : location.getWorld().getPlayers()) { - if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_distance, 2)) { - if (shop.getHologram() != null) { - shop.getHologram().showPlayer(p); + plugin.debug("Shop created (#" + id + ")"); + shopUtils.addShop(shop, true); + executor.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_CREATED)); + + for (Player p : location.getWorld().getPlayers()) { + if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_distance, 2)) { + if (shop.getHologram() != null) { + shop.getHologram().showPlayer(p); + } + } + if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_item_distance, 2)) { + if (shop.getItem() != null) { + shop.getItem().setVisible(p, true); + } + } + } } } - if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_item_distance, 2)) { - if (shop.getItem() != null) { - shop.getItem().setVisible(p, true); - } - } - } + @Override + public void onError(Throwable throwable) {} + }); } /** @@ -708,7 +716,7 @@ public class ShopInteractListener implements Listener { return; } - database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.BUY); + database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.BUY, null); addToInventory(inventory, newProduct); removeFromInventory(c.getInventory(), newProduct); @@ -742,7 +750,7 @@ public class ShopInteractListener implements Listener { return; } - database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.BUY); + database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.BUY, null); addToInventory(inventory, newProduct); executor.updateInventory(); @@ -847,7 +855,7 @@ public class ShopInteractListener implements Listener { return; } - database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.SELL); + database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.SELL, null); addToInventory(inventory, newProduct); removeFromInventory(executor.getInventory(), newProduct); @@ -882,7 +890,7 @@ public class ShopInteractListener implements Listener { return; } - database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.SELL); + database.logEconomy(executor, newProduct, shop.getVendor(), shop.getShopType(), shop.getLocation(), newPrice, ShopBuySellEvent.Type.SELL, null); removeFromInventory(executor.getInventory(), newProduct); executor.updateInventory(); diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java index 0f59479..bb83866 100644 --- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java +++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java @@ -150,29 +150,44 @@ public class Hologram { /** * @param p Player from which the hologram should be hidden */ - public void hidePlayer(final Player p) { - new BukkitRunnable() { - @Override - public void run() { - for (Object o : entityList) { - try { - int id = (int) entityArmorStandClass.getMethod("getId").invoke(o); - - Object packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[] {id}); - - Utils.sendPacket(plugin, packet, p); - } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { - plugin.getLogger().severe("Could not hide Hologram from player with reflection"); - plugin.debug("Could not hide Hologram from player with reflection"); - plugin.debug(e); - } + public void hidePlayer(final Player p, boolean useCurrentThread) { + if (useCurrentThread) { + sendDestroyPackets(p); + } else { + new BukkitRunnable() { + @Override + public void run() { + sendDestroyPackets(p); } - } - }.runTaskAsynchronously(plugin); + }.runTaskAsynchronously(plugin); + } visible.remove(p); } + private void sendDestroyPackets(Player p) { + for (Object o : entityList) { + try { + int id = (int) entityArmorStandClass.getMethod("getId").invoke(o); + + Object packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[] {id}); + + Utils.sendPacket(plugin, packet, p); + } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + plugin.getLogger().severe("Could not hide Hologram from player with reflection"); + plugin.debug("Could not hide Hologram from player with reflection"); + plugin.debug(e); + } + } + } + + /** + * @param p Player from which the hologram should be hidden + */ + public void hidePlayer(final Player p) { + hidePlayer(p, false); + } + /** * @param p Player to check * @return Whether the hologram is visible to the player diff --git a/src/main/java/de/epiceric/shopchest/shop/Shop.java b/src/main/java/de/epiceric/shopchest/shop/Shop.java index f24c6bd..dfe1ef3 100644 --- a/src/main/java/de/epiceric/shopchest/shop/Shop.java +++ b/src/main/java/de/epiceric/shopchest/shop/Shop.java @@ -2,6 +2,8 @@ package de.epiceric.shopchest.shop; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.config.Regex; +import de.epiceric.shopchest.exceptions.ChestNotFoundException; +import de.epiceric.shopchest.exceptions.NotEnoughSpaceException; import de.epiceric.shopchest.language.LanguageUtils; import de.epiceric.shopchest.language.LocalizedMessage; import de.epiceric.shopchest.nms.Hologram; @@ -19,6 +21,7 @@ import org.bukkit.inventory.ItemStack; public class Shop { + private boolean created; private int id; private ShopChest plugin; private OfflinePlayer vendor; @@ -39,22 +42,6 @@ public class Shop { this.buyPrice = buyPrice; this.sellPrice = sellPrice; this.shopType = shopType; - - Block b = location.getBlock(); - if (b.getType() != Material.CHEST && b.getType() != Material.TRAPPED_CHEST) { - plugin.getShopUtils().removeShop(this, plugin.getShopChestConfig().remove_shop_on_error); - plugin.getLogger().severe("No Chest found at specified Location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); - plugin.debug("No Chest found at specified Location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); - return; - } else if ((b.getRelative(BlockFace.UP).getType() != Material.AIR) && plugin.getShopChestConfig().show_shop_items) { - plugin.getShopUtils().removeShop(this, plugin.getShopChestConfig().remove_shop_on_error); - plugin.getLogger().severe("No space above chest at specified Location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); - plugin.debug("No space above chest at specified Location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); - return; - } - - if (hologram == null || !hologram.exists()) createHologram(); - if (item == null) createItem(); } private Shop(OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) { @@ -67,21 +54,54 @@ public class Shop { this.shopType = shopType; } + public void create() { + if (created) return; + + Block b = location.getBlock(); + if (b.getType() != Material.CHEST && b.getType() != Material.TRAPPED_CHEST) { + ChestNotFoundException ex = new ChestNotFoundException("No Chest found at location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); + plugin.getShopUtils().removeShop(this, plugin.getShopChestConfig().remove_shop_on_error); + plugin.getLogger().severe(ex.getMessage()); + plugin.debug("Failed to create shop (#" + id + ")"); + plugin.debug(ex); + return; + } else if ((b.getRelative(BlockFace.UP).getType() != Material.AIR) && plugin.getShopChestConfig().show_shop_items) { + NotEnoughSpaceException ex = new NotEnoughSpaceException("No space above chest at location: " + b.getX() + "; " + b.getY() + "; " + b.getZ()); + plugin.getShopUtils().removeShop(this, plugin.getShopChestConfig().remove_shop_on_error); + plugin.getLogger().severe(ex.getMessage()); + plugin.debug("Failed to create shop (#" + id + ")"); + plugin.debug(ex); + return; + } + + if (hologram == null || !hologram.exists()) createHologram(); + if (item == null) createItem(); + + created = true; + } + /** - * Creates the hologram of the shop if it doesn't exist + * Removes the hologram of the shop */ - public void removeHologram() { + public void removeHologram(boolean useCurrentThread) { if (hologram != null && hologram.exists()) { plugin.debug("Removing hologram (#" + id + ")"); for (Player p : Bukkit.getOnlinePlayers()) { - hologram.hidePlayer(p); + hologram.hidePlayer(p, useCurrentThread); } hologram.remove(); } } + /** + * Removes the hologram of the shop + */ + public void removeHologram() { + removeHologram(false); + } + /** * Removes the floating item of the shop */ @@ -203,6 +223,13 @@ public class Shop { hologram = new Hologram(plugin, holoText, holoLocation); } + /** + * @return Whether the shop has already been created + */ + public boolean isCreated() { + return created; + } + /** * @return The ID of the shop */ diff --git a/src/main/java/de/epiceric/shopchest/sql/Database.java b/src/main/java/de/epiceric/shopchest/sql/Database.java index c0870e6..6c3c069 100644 --- a/src/main/java/de/epiceric/shopchest/sql/Database.java +++ b/src/main/java/de/epiceric/shopchest/sql/Database.java @@ -2,9 +2,11 @@ package de.epiceric.shopchest.sql; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.event.ShopBuySellEvent; +import de.epiceric.shopchest.exceptions.WorldNotFoundException; import de.epiceric.shopchest.language.LanguageUtils; import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.shop.Shop.ShopType; +import de.epiceric.shopchest.utils.Callback; import de.epiceric.shopchest.utils.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -12,6 +14,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; import java.sql.*; import java.text.SimpleDateFormat; @@ -36,117 +39,151 @@ public abstract class Database { * (Re-)Connects to the the database and initializes it.
* Creates the table (if doesn't exist) and tests the connection */ - public void connect() { - try { - disconnect(); + public void connect(final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + try { + disconnect(); - plugin.debug("Connecting to database..."); - connection = getConnection(); + plugin.debug("Connecting to database..."); + connection = getConnection(); - String queryCreateTableShopList = - "CREATE TABLE IF NOT EXISTS shop_list (" + - "`id` int(11) NOT NULL," + - "`vendor` tinytext NOT NULL," + - "`product` text NOT NULL," + - "`world` tinytext NOT NULL," + - "`x` int(11) NOT NULL," + - "`y` int(11) NOT NULL," + - "`z` int(11) NOT NULL," + - "`buyprice` float(32) NOT NULL," + - "`sellprice` float(32) NOT NULL," + - "`shoptype` tinytext NOT NULL," + - "PRIMARY KEY (`id`)" + - ");"; + String queryCreateTableShopList = + "CREATE TABLE IF NOT EXISTS shop_list (" + + "`id` int(11) NOT NULL," + + "`vendor` tinytext NOT NULL," + + "`product` text NOT NULL," + + "`world` tinytext NOT NULL," + + "`x` int(11) NOT NULL," + + "`y` int(11) NOT NULL," + + "`z` int(11) NOT NULL," + + "`buyprice` float(32) NOT NULL," + + "`sellprice` float(32) NOT NULL," + + "`shoptype` tinytext NOT NULL," + + "PRIMARY KEY (`id`)" + + ");"; - String queryCreateTableShopLog = - "CREATE TABLE IF NOT EXISTS shop_log (" + - "`id` INTEGER PRIMARY KEY " + (this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT") + "," + - "`timestamp` TINYTEXT NOT NULL," + - "`executor` TINYTEXT NOT NULL," + - "`product` TINYTEXT NOT NULL," + - "`vendor` TINYTEXT NOT NULL," + - "`world` TINYTEXT NOT NULL," + - "`x` INTEGER NOT NULL," + - "`y` INTEGER NOT NULL," + - "`z` INTEGER NOT NULL," + - "`price` FLOAT NOT NULL," + - "`type` TINYTEXT NOT NULL" + - ");"; + String queryCreateTableShopLog = + "CREATE TABLE IF NOT EXISTS shop_log (" + + "`id` INTEGER PRIMARY KEY " + (Database.this instanceof SQLite ? "AUTOINCREMENT" : "AUTO_INCREMENT") + "," + + "`timestamp` TINYTEXT NOT NULL," + + "`executor` TINYTEXT NOT NULL," + + "`product` TINYTEXT NOT NULL," + + "`vendor` TINYTEXT NOT NULL," + + "`world` TINYTEXT NOT NULL," + + "`x` INTEGER NOT NULL," + + "`y` INTEGER NOT NULL," + + "`z` INTEGER NOT NULL," + + "`price` FLOAT NOT NULL," + + "`type` TINYTEXT NOT NULL" + + ");"; - // Create table "shop_list" - Statement s = connection.createStatement(); - s.executeUpdate(queryCreateTableShopList); - s.close(); + // Create table "shop_list" + Statement s = connection.createStatement(); + s.executeUpdate(queryCreateTableShopList); + s.close(); - // Create table "shop_log" - Statement s2 = connection.createStatement(); - s2.executeUpdate(queryCreateTableShopLog); - s2.close(); + // Create table "shop_log" + Statement s2 = connection.createStatement(); + s2.executeUpdate(queryCreateTableShopLog); + s2.close(); - // Count entries in table "shop_list" - PreparedStatement ps = connection.prepareStatement("SELECT * FROM shop_list"); - ResultSet rs = ps.executeQuery(); + // Count entries in table "shop_list" + PreparedStatement ps = connection.prepareStatement("SELECT * FROM shop_list"); + ResultSet rs = ps.executeQuery(); - int count = 0; - while (rs.next()) { - if (rs.getString("vendor") != null) count++; + int count = 0; + while (rs.next()) { + if (rs.getString("vendor") != null) count++; + } + plugin.debug("Initialized database with " + count + " entries"); + + close(ps, rs); + + if (callback != null) callback.callSyncResult(count); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.getLogger().severe("Failed to connect to database"); + plugin.debug("Failed to connect to database"); + plugin.debug(ex); + } } - plugin.debug("Initialized database with " + count + " entries"); - - close(ps, rs); - - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to connect to database"); - plugin.debug("Failed to connect to database"); - plugin.debug(ex); - } + }.runTaskAsynchronously(plugin); } /** * @return Lowest possible ID which is not used (> 0) */ - public int getNextFreeID() { - int highestId = getHighestID(); - for (int i = 1; i <= highestId + 1; i++) { - if (!isShop(i)) { - plugin.debug("Next free id: " + i); - return i; - } - } + public void getNextFreeID(final Callback callback) { + getHighestID(new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Integer) { + int highestId = (int) result; - return 1; + for (int i = 1; i <= highestId + 1; i++) { + final int id = i; + isShop(i, new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Boolean) { + boolean isShop = (boolean) result; + if (!isShop) { + if (callback != null) callback.callSyncResult(id); + } + } + } + + @Override + public void onError(Throwable throwable) { + if (callback != null) callback.callSyncError(throwable); + } + }); + } + } + } + + @Override + public void onError(Throwable throwable) { + if (callback != null) callback.callSyncError(throwable); + } + }); } /** * @return Highest ID which is used */ - public int getHighestID() { - PreparedStatement ps = null; - ResultSet rs = null; + public void getHighestID(final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + PreparedStatement ps = null; + ResultSet rs = null; - int highestID = 0; + int highestID = 0; - try { - ps = connection.prepareStatement("SELECT * FROM shop_list;"); - rs = ps.executeQuery(); + try { + ps = connection.prepareStatement("SELECT * FROM shop_list;"); + rs = ps.executeQuery(); - while (rs.next()) { - if (rs.getInt("id") > highestID) { - highestID = rs.getInt("id"); + while (rs.next()) { + if (rs.getInt("id") > highestID) { + highestID = rs.getInt("id"); + } + } + + plugin.debug("Highest used ID: " + highestID); + if (callback != null) callback.callSyncResult(highestID); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.debug("Failed to get highest used ID"); + plugin.getLogger().severe("Failed to access database"); + } finally { + close(ps, rs); } } - - plugin.debug("Highest used ID: " + highestID); - return highestID; - - } catch (SQLException ex) { - plugin.debug("Failed to get highest used ID"); - plugin.getLogger().severe("Failed to access database"); - } finally { - close(ps, rs); - } - - return 0; + }.runTaskAsynchronously(plugin); } /** @@ -154,135 +191,172 @@ public abstract class Database { * * @param shop Shop to remove */ - public void removeShop(Shop shop) { - PreparedStatement ps = null; - - try { - ps = connection.prepareStatement("DELETE FROM shop_list WHERE id = " + shop.getID() + ";"); - plugin.debug("Removing shop from database (#" + shop.getID() + ")"); - ps.executeUpdate(); - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to access database"); - plugin.debug("Failed to remove shop from database (#" + shop.getID() + ")"); - plugin.debug(ex); - } finally { - close(ps, null); - } + public void removeShop(final Shop shop, final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + PreparedStatement ps = null; + try { + ps = connection.prepareStatement("DELETE FROM shop_list WHERE id = " + shop.getID() + ";"); + plugin.debug("Removing shop from database (#" + shop.getID() + ")"); + ps.executeUpdate(); + if (callback != null) callback.callSyncResult(null); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.getLogger().severe("Failed to access database"); + plugin.debug("Failed to remove shop from database (#" + shop.getID() + ")"); + plugin.debug(ex); + } finally { + close(ps, null); + } + } + }.runTaskAsynchronously(plugin); } /** * @param id ID of the shop * @return Whether a shop with the given ID exists */ - public boolean isShop(int id) { - PreparedStatement ps = null; - ResultSet rs = null; + public void isShop(final int id, final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + PreparedStatement ps = null; + ResultSet rs = null; - try { - ps = connection.prepareStatement("SELECT * FROM shop_list WHERE id = " + id + ";"); - rs = ps.executeQuery(); + try { + ps = connection.prepareStatement("SELECT * FROM shop_list WHERE id = " + id + ";"); + rs = ps.executeQuery(); - while (rs.next()) { - if (rs.getInt("id") == id) { - return true; + while (rs.next()) { + if (rs.getInt("id") == id) { + if (callback != null) callback.callSyncResult(true); + return; + } + } + + if (callback != null) callback.callSyncResult(false); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.getLogger().severe("Failed to access database"); + plugin.debug("Failed to check if shop with ID exists (#" + id + ")"); + plugin.debug(ex); + } finally { + close(ps, rs); } } - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to access database"); - plugin.debug("Failed to check if shop with ID exists (#" + id + ")"); - plugin.debug(ex); - } finally { - close(ps, rs); - } - - return false; + }.runTaskAsynchronously(plugin); } /** * @param id ID of the shop * @return Shop with the given ID */ - public Shop getShop(int id) { - PreparedStatement ps = null; - ResultSet rs = null; + public void getShop(final int id, final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + PreparedStatement ps = null; + ResultSet rs = null; - try { - ps = connection.prepareStatement("SELECT * FROM shop_list WHERE id = " + id + ";"); - rs = ps.executeQuery(); + try { + ps = connection.prepareStatement("SELECT * FROM shop_list WHERE id = " + id + ";"); + rs = ps.executeQuery(); - while (rs.next()) { - if (rs.getInt("id") == id) { - plugin.debug("Getting Shop... (#" + id + ")"); + while (rs.next()) { + if (rs.getInt("id") == id) { + plugin.debug("Getting Shop... (#" + id + ")"); - World world = Bukkit.getWorld(rs.getString("world")); - int x = rs.getInt("x"); - int y = rs.getInt("y"); - int z = rs.getInt("z"); + String worldName = rs.getString("world"); + World world = Bukkit.getWorld(worldName); + int x = rs.getInt("x"); + int y = rs.getInt("y"); + int z = rs.getInt("z"); - Location location = new Location(world, x, y, z); + if (world == null) { + WorldNotFoundException ex = new WorldNotFoundException("Could not find world with name \"" + worldName + "\""); + callback.callSyncError(ex); + plugin.getLogger().warning(ex.getMessage()); + plugin.debug("Failed to get shop (#" + id + ")"); + plugin.debug(ex); + return; + } - Shop shop = plugin.getShopUtils().getShop(location); - if (shop != null) { - plugin.debug("Shop already exists, returning existing one (#" + id + ")."); - return shop; - } else { - plugin.debug("Creating new shop... (#" + id + ")"); + Location location = new Location(world, x, y, z); - OfflinePlayer vendor = Bukkit.getOfflinePlayer(UUID.fromString(rs.getString("vendor"))); - ItemStack product = Utils.decode(rs.getString("product")); - double buyPrice = rs.getDouble("buyprice"); - double sellPrice = rs.getDouble("sellprice"); - ShopType shopType = ShopType.valueOf(rs.getString("shoptype")); + Shop shop = plugin.getShopUtils().getShop(location); + if (shop != null) { + plugin.debug("Shop already exists, returning existing one (#" + id + ")."); + if (callback != null) callback.callSyncResult(shop); + } else { + plugin.debug("Creating new shop... (#" + id + ")"); - return new Shop(id, plugin, vendor, product, location, buyPrice, sellPrice, shopType); + OfflinePlayer vendor = Bukkit.getOfflinePlayer(UUID.fromString(rs.getString("vendor"))); + ItemStack product = Utils.decode(rs.getString("product")); + double buyPrice = rs.getDouble("buyprice"); + double sellPrice = rs.getDouble("sellprice"); + ShopType shopType = ShopType.valueOf(rs.getString("shoptype")); + + if (callback != null) callback.callSyncResult(new Shop(id, plugin, vendor, product, location, buyPrice, sellPrice, shopType)); + } + + return; + } } + + plugin.debug("Shop with ID not found, returning null. (#" + id + ")"); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.getLogger().severe("Failed to access database"); + plugin.debug("Failed to get shop (#" + id + ")"); + plugin.debug(ex); + } finally { + close(ps, rs); } + + if (callback != null) callback.callSyncResult(null); } - - plugin.debug("Shop with ID not found, returning null. (#" + id + ")"); - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to access database"); - plugin.debug("Failed to get shop (#" + id + ")"); - plugin.debug(ex); - } finally { - close(ps, rs); - } - - return null; + }.runTaskAsynchronously(plugin); } /** * Adds a shop to the database * @param shop Shop to add */ - public void addShop(Shop shop) { - PreparedStatement ps = null; + public void addShop(final Shop shop, final Callback callback) { + new BukkitRunnable() { + @Override + public void run() { + PreparedStatement ps = null; - try { - ps = connection.prepareStatement("REPLACE INTO shop_list (id,vendor,product,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?)"); + try { + ps = connection.prepareStatement("REPLACE INTO shop_list (id,vendor,product,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?)"); - ps.setInt(1, shop.getID()); - ps.setString(2, shop.getVendor().getUniqueId().toString()); - ps.setString(3, Utils.encode(shop.getProduct())); - ps.setString(4, shop.getLocation().getWorld().getName()); - ps.setInt(5, shop.getLocation().getBlockX()); - ps.setInt(6, shop.getLocation().getBlockY()); - ps.setInt(7, shop.getLocation().getBlockZ()); - ps.setDouble(8, shop.getBuyPrice()); - ps.setDouble(9, shop.getSellPrice()); - ps.setString(10, shop.getShopType().toString()); + ps.setInt(1, shop.getID()); + ps.setString(2, shop.getVendor().getUniqueId().toString()); + ps.setString(3, Utils.encode(shop.getProduct())); + ps.setString(4, shop.getLocation().getWorld().getName()); + ps.setInt(5, shop.getLocation().getBlockX()); + ps.setInt(6, shop.getLocation().getBlockY()); + ps.setInt(7, shop.getLocation().getBlockZ()); + ps.setDouble(8, shop.getBuyPrice()); + ps.setDouble(9, shop.getSellPrice()); + ps.setString(10, shop.getShopType().toString()); + ps.executeUpdate(); - plugin.debug("Adding shop to database (#" + shop.getID() + ")"); - - ps.executeUpdate(); - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to access database"); - plugin.debug("Failed to add shop to database (#" + shop.getID() + ")"); - plugin.debug(ex); - } finally { - close(ps, null); - } + if (callback != null) callback.callSyncResult(null); + plugin.debug("Adding shop to database (#" + shop.getID() + ")"); + } catch (SQLException ex) { + if (callback != null) callback.callSyncError(ex); + plugin.getLogger().severe("Failed to access database"); + plugin.debug("Failed to add shop to database (#" + shop.getID() + ")"); + plugin.debug(ex); + } finally { + close(ps, null); + } + } + }.runTaskAsynchronously(plugin); } /** @@ -294,14 +368,11 @@ public abstract class Database { * @param price Price (buyprice or sellprice, depends on {@code type}) * @param type Whether the player bought or sold something */ - public void logEconomy(final Player executor, final ItemStack product, final OfflinePlayer vendor, final ShopType shopType, final Location location, final double price, final ShopBuySellEvent.Type type) { - - Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() { + public void logEconomy(final Player executor, final ItemStack product, final OfflinePlayer vendor, final ShopType shopType, final Location location, final double price, final ShopBuySellEvent.Type type, final Callback callback) { + new BukkitRunnable() { @Override public void run() { - PreparedStatement ps = null; - boolean debugLogEnabled = plugin.getShopChestConfig().enable_debug_log; try { ps = connection.prepareStatement("INSERT INTO shop_log (timestamp,executor,product,vendor,world,x,y,z,price,type) VALUES(?,?,?,?,?,?,?,?,?,?)"); @@ -316,33 +387,20 @@ public abstract class Database { ps.setInt(8, location.getBlockZ()); ps.setDouble(9, price); ps.setString(10, type.toString()); - ps.executeUpdate(); - if (debugLogEnabled) { - Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - plugin.debug("Logged economy transaction to database"); - } - }); - } + if (callback != null) callback.callSyncResult(null); + plugin.debug("Logged economy transaction to database"); } catch (final SQLException ex) { + if (callback != null) callback.callSyncError(ex); plugin.getLogger().severe("Failed to access database"); - if (debugLogEnabled) { - Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - plugin.debug("Failed to log economy transaction to database"); - plugin.debug(ex); - } - }); - } + plugin.debug("Failed to log economy transaction to database"); + plugin.debug(ex); } finally { close(ps, null); } } - }); + }.runTaskAsynchronously(plugin); } /** diff --git a/src/main/java/de/epiceric/shopchest/sql/MySQL.java b/src/main/java/de/epiceric/shopchest/sql/MySQL.java index 2039ef1..db82de4 100644 --- a/src/main/java/de/epiceric/shopchest/sql/MySQL.java +++ b/src/main/java/de/epiceric/shopchest/sql/MySQL.java @@ -1,6 +1,7 @@ package de.epiceric.shopchest.sql; import de.epiceric.shopchest.ShopChest; +import org.bukkit.scheduler.BukkitRunnable; import java.sql.Connection; import java.sql.DriverManager; @@ -38,13 +39,18 @@ public class MySQL extends Database { } public void ping() { - try (PreparedStatement ps = connection.prepareStatement("/* ping */ SELECT 1")) { - plugin.debug("Pinging to MySQL server..."); - ps.executeQuery(); - } catch (SQLException ex) { - plugin.getLogger().severe("Failed to ping to MySQL server. Trying to reconnect..."); - plugin.debug("Failed to ping to MySQL server. Trying to reconnect..."); - connect(); - } + new BukkitRunnable() { + @Override + public void run() { + try (PreparedStatement ps = connection.prepareStatement("/* ping */ SELECT 1")) { + plugin.debug("Pinging to MySQL server..."); + ps.executeQuery(); + } catch (SQLException ex) { + plugin.getLogger().severe("Failed to ping to MySQL server. Trying to reconnect..."); + plugin.debug("Failed to ping to MySQL server. Trying to reconnect..."); + connect(null); + } + } + }.runTaskAsynchronously(plugin); } } diff --git a/src/main/java/de/epiceric/shopchest/utils/Callback.java b/src/main/java/de/epiceric/shopchest/utils/Callback.java new file mode 100644 index 0000000..16c0982 --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/utils/Callback.java @@ -0,0 +1,34 @@ +package de.epiceric.shopchest.utils; + +import de.epiceric.shopchest.ShopChest; +import org.bukkit.scheduler.BukkitRunnable; + +public abstract class Callback { + private ShopChest plugin; + + public Callback(ShopChest plugin) { + this.plugin = plugin; + } + + public void onResult(Object result) {} + + public void onError(Throwable throwable) {} + + public void callSyncResult(final Object result) { + new BukkitRunnable() { + @Override + public void run() { + onResult(result); + } + }.runTask(plugin); + } + + public void callSyncError(final Throwable throwable) { + new BukkitRunnable() { + @Override + public void run() { + onError(throwable); + } + }.runTask(plugin); + } +} diff --git a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java index 7473cf1..f344828 100644 --- a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java +++ b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java @@ -86,7 +86,7 @@ public class ShopUtils { } if (addToDatabase) - plugin.getShopDatabase().addShop(shop); + plugin.getShopDatabase().addShop(shop, null); } @@ -95,7 +95,7 @@ public class ShopUtils { * @param shop Shop to remove * @param removeFromDatabase Whether the shop should also be removed from the database */ - public void removeShop(Shop shop, boolean removeFromDatabase) { + public void removeShop(Shop shop, boolean removeFromDatabase, boolean useCurrentThread) { plugin.debug("Removing shop (#" + shop.getID() + ")"); InventoryHolder ih = shop.getInventoryHolder(); @@ -112,10 +112,14 @@ public class ShopUtils { } shop.removeItem(); - shop.removeHologram(); + shop.removeHologram(useCurrentThread); if (removeFromDatabase) - plugin.getShopDatabase().removeShop(shop); + plugin.getShopDatabase().removeShop(shop, null); + } + + public void removeShop(Shop shop, boolean removeFromDatabase) { + removeShop(shop, removeFromDatabase, false); } /** @@ -189,40 +193,61 @@ public class ShopUtils { * @param showConsoleMessages Whether messages about the language file should be shown in the console * @return Amount of shops, which were reloaded */ - public int reloadShops(boolean reloadConfig, boolean showConsoleMessages) { + public void reloadShops(boolean reloadConfig, boolean showConsoleMessages, final Callback callback) { plugin.debug("Reloading shops..."); - plugin.getShopDatabase().connect(); - if (reloadConfig) { plugin.getShopChestConfig().reload(false, true, showConsoleMessages); plugin.getUpdater().setMaxDelta(plugin.getShopChestConfig().update_quality.getTime()); } - for (Shop shop : getShops()) { - removeShop(shop, false); - plugin.debug("Removed shop (#" + shop.getID() + ")"); - } + plugin.getShopDatabase().connect(new Callback(plugin) { + @Override + public void onResult(Object result) { - int highestId = plugin.getShopDatabase().getHighestID(); + for (Shop shop : getShops()) { + removeShop(shop, false); + plugin.debug("Removed shop (#" + shop.getID() + ")"); + } - int count = 0; - for (int id = 1; id <= highestId; id++) { + plugin.getShopDatabase().getHighestID(new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Integer) { + int highestId = (int) result; + + int count = 0; + for (int i = 1; i <= highestId; i++) { + final int id = i; + + plugin.debug("Trying to add shop. (#" + id + ")"); + plugin.getShopDatabase().getShop(id, new Callback(plugin) { + @Override + public void onResult(Object result) { + if (result instanceof Shop) { + Shop shop = (Shop) result; + shop.create(); + addShop(shop, false); + } + } + + @Override + public void onError(Throwable throwable) { + plugin.debug("Error while adding shop (#" + id + "):"); + plugin.debug(throwable); + } + }); + + count++; + } + + if (callback != null) callback.callSyncResult(count); + } + } + }); - try { - plugin.debug("Trying to add shop. (#" + id + ")"); - Shop shop = plugin.getShopDatabase().getShop(id); - addShop(shop, false); - } catch (Exception e) { - plugin.debug("Error while adding shop (#" + id + "):"); - plugin.debug(e); - continue; } - - count++; - } - - return count; + }); } /**