From e4691102ae5c90c7f477aeb81b7d06e38165114e Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 14 Nov 2023 23:28:56 +0100 Subject: [PATCH] lots of improvements and bugfixes. --- pom.xml | 22 ++- src/main/java/tsp/headdb/HeadDB.java | 66 +++++-- src/main/java/tsp/headdb/Metrics.java | 4 +- .../java/tsp/headdb/core/api/HeadAPI.java | 19 +- .../tsp/headdb/core/command/CommandMain.java | 7 +- .../headdb/core/command/CommandUpdate.java | 15 +- .../core/economy/BasicEconomyProvider.java | 10 +- .../headdb/core/economy/VaultProvider.java | 10 +- .../headdb/core/storage/PlayerStorage.java | 2 +- .../java/tsp/headdb/core/storage/Storage.java | 5 +- .../java/tsp/headdb/core/task/UpdateTask.java | 49 +++-- .../tsp/headdb/core/util/HeadDBLogger.java | 57 +----- src/main/java/tsp/headdb/core/util/Utils.java | 172 ++++++++++++------ .../tsp/headdb/implementation/head/Head.java | 31 +--- .../implementation/head/HeadDatabase.java | 36 ++-- .../implementation/head/HeadResult.java | 11 ++ .../implementation/requester/Requester.java | 76 ++++---- src/main/resources/config.yml | 5 - 18 files changed, 312 insertions(+), 285 deletions(-) create mode 100644 src/main/java/tsp/headdb/implementation/head/HeadResult.java diff --git a/pom.xml b/pom.xml index 9c15ddd..9e62868 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ tsp.headdb HeadDB - 5.0.0-rc.7 + 5.0.0-rc.8 jar HeadDB @@ -55,13 +55,13 @@ org.spigotmc spigot-api - 1.20.1-R0.1-SNAPSHOT + 1.20.2-R0.1-SNAPSHOT provided com.mojang authlib - 1.5.21 + 4.0.43 provided @@ -75,20 +75,26 @@ com.github.TheSilentPro NexusLib - c01a0a0a7d + 9d8b16b770 com.github.TheSilentPro Warehouse - 882b42fc75 + b228e4f8b1 net.wesjd anvilgui - 1.7.0-SNAPSHOT + 1.9.0-SNAPSHOT + + + com.github.TheSilentPro + HelperLite + 775582f23b + com.github.MilkBowl VaultAPI @@ -98,7 +104,7 @@ me.clip placeholderapi - 2.11.1 + 2.11.5 provided @@ -183,7 +189,7 @@ org.spigotmc spigot-api - 1.20.1-R0.1-SNAPSHOT + 1.20.2-R0.1-SNAPSHOT diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java index fac900d..c4bb72e 100644 --- a/src/main/java/tsp/headdb/HeadDB.java +++ b/src/main/java/tsp/headdb/HeadDB.java @@ -7,17 +7,25 @@ import tsp.headdb.core.economy.VaultProvider; import tsp.headdb.core.storage.Storage; import tsp.headdb.core.task.UpdateTask; import tsp.headdb.core.util.HeadDBLogger; +import tsp.helperlite.HelperLite; +import tsp.helperlite.Schedulers; +import tsp.helperlite.scheduler.promise.Promise; +import tsp.helperlite.scheduler.task.Task; import tsp.nexuslib.NexusPlugin; import tsp.nexuslib.inventory.PaneListener; import tsp.nexuslib.localization.TranslatableLocalization; -import tsp.nexuslib.util.PluginUtils; +import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; import java.text.DecimalFormat; import java.util.Optional; +import java.util.concurrent.TimeUnit; public class HeadDB extends NexusPlugin { @@ -27,22 +35,27 @@ public class HeadDB extends NexusPlugin { private Storage storage; private BasicEconomyProvider economyProvider; private CommandManager commandManager; + private Task updateTask; @Override public void onStart(NexusPlugin nexusPlugin) { instance = this; + HelperLite.init(this); + instance.saveDefaultConfig(); instance.logger = new HeadDBLogger(getConfig().getBoolean("debug")); instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion()); - new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this); instance.logger.info("Loaded " + loadLocalization() + " languages!"); instance.initStorage(); instance.initEconomy(); + startUpdateTask(); + new PaneListener(this); + // TODO: Commands helperlite instance.commandManager = new CommandManager(); loadCommands(); @@ -66,8 +79,38 @@ public class HeadDB extends NexusPlugin { } } } + + updateTask.stop(); } + private void startUpdateTask() { + updateTask = Schedulers.builder() + .async() + .every(getConfig().getLong("refresh", 86400L), TimeUnit.SECONDS) + .run(new UpdateTask()); + } + + private void ensureLatestVersion() { + Promise.start().thenApplyAsync(a -> { + try { + URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + 84967).openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestProperty("User-Agent", this.getName() + "-VersionChecker"); + + return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(this.getDescription().getVersion()); + } catch (IOException ex) { + return false; + } + }).thenAcceptAsync(latest -> { + if (latest) { + instance.logger.warning("There is a new update available for HeadDB on spigot!"); + instance.logger.warning("Download: https://www.spigotmc.org/resources/84967"); + } + }); + } + + // Loaders + private void initMetrics() { Metrics metrics = new Metrics(this, 9152); @@ -80,19 +123,8 @@ public class HeadDB extends NexusPlugin { })); } - private void ensureLatestVersion() { - PluginUtils.isLatestVersion(this, 84967, latest -> { - if (Boolean.FALSE.equals(latest)) { - instance.logger.warning("There is a new update available for HeadDB on spigot!"); - instance.logger.warning("Download: https://www.spigotmc.org/resources/84967"); - } - }); - } - - // Loaders - private void initStorage() { - storage = new Storage(getConfig().getInt("storage.threads")); + storage = new Storage(); storage.getPlayerStorage().init(); } @@ -156,6 +188,10 @@ public class HeadDB extends NexusPlugin { // Getters + public Optional getUpdateTask() { + return Optional.ofNullable(updateTask); + } + public Storage getStorage() { return storage; } diff --git a/src/main/java/tsp/headdb/Metrics.java b/src/main/java/tsp/headdb/Metrics.java index 1fd1a2f..9733d08 100644 --- a/src/main/java/tsp/headdb/Metrics.java +++ b/src/main/java/tsp/headdb/Metrics.java @@ -5,6 +5,7 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import tsp.headdb.core.storage.HeadDBThreadFactory; import javax.net.ssl.HttpsURLConnection; import java.io.*; @@ -132,8 +133,7 @@ class Metrics { /** The version of the Metrics class. */ public static final String METRICS_VERSION = "3.0.0"; - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(HeadDBThreadFactory.FACTORY); private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; diff --git a/src/main/java/tsp/headdb/core/api/HeadAPI.java b/src/main/java/tsp/headdb/core/api/HeadAPI.java index 96c0a6c..0bf0a02 100644 --- a/src/main/java/tsp/headdb/core/api/HeadAPI.java +++ b/src/main/java/tsp/headdb/core/api/HeadAPI.java @@ -9,6 +9,7 @@ import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.HeadDatabase; import tsp.headdb.implementation.head.LocalHead; import tsp.headdb.implementation.requester.HeadProvider; +import tsp.helperlite.scheduler.promise.Promise; import javax.annotation.Nonnull; import java.util.*; @@ -163,14 +164,16 @@ public final class HeadAPI { * @return {@link Set Favorite Heads} */ @Nonnull - public static List getFavoriteHeads(UUID player) { - List result = new ArrayList<>(); - Optional data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player); - data.ifPresent(playerData -> playerData.favorites() - .forEach(texture -> getHeadByTexture(texture) - .ifPresent(result::add)) - ); - return result; + public static Promise> getFavoriteHeads(UUID player) { + return Promise.supplyingAsync(() -> { + List result = new ArrayList<>(); + Optional data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player); + data.ifPresent(playerData -> playerData.favorites() + .forEach(texture -> getHeadByTexture(texture) + .ifPresent(result::add)) + ); + return result; + }); } /** diff --git a/src/main/java/tsp/headdb/core/command/CommandMain.java b/src/main/java/tsp/headdb/core/command/CommandMain.java index b9005ff..9d1c512 100644 --- a/src/main/java/tsp/headdb/core/command/CommandMain.java +++ b/src/main/java/tsp/headdb/core/command/CommandMain.java @@ -1,8 +1,6 @@ package tsp.headdb.core.command; -import java.util.Arrays; import net.wesjd.anvilgui.AnvilGUI; -import net.wesjd.anvilgui.AnvilGUI.Builder; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.command.Command; @@ -24,10 +22,7 @@ import tsp.nexuslib.util.StringUtils; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter { diff --git a/src/main/java/tsp/headdb/core/command/CommandUpdate.java b/src/main/java/tsp/headdb/core/command/CommandUpdate.java index f66124d..2da6f89 100644 --- a/src/main/java/tsp/headdb/core/command/CommandUpdate.java +++ b/src/main/java/tsp/headdb/core/command/CommandUpdate.java @@ -1,9 +1,10 @@ package tsp.headdb.core.command; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import tsp.headdb.HeadDB; import tsp.headdb.core.api.HeadAPI; +import tsp.headdb.implementation.head.HeadResult; +import tsp.helperlite.scheduler.promise.Promise; public class CommandUpdate extends SubCommand { @@ -14,10 +15,14 @@ public class CommandUpdate extends SubCommand { @Override public void handle(CommandSender sender, String[] args) { getLocalization().sendMessage(sender, "updateDatabase"); - HeadAPI.getDatabase().update((time, result) -> { - HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms"); - Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.values().size())))); - }); + try (Promise promise = HeadAPI.getDatabase().update()) { + promise.thenAcceptSync(result -> { + HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.heads().values().size() + " | Took: " + result.elapsed() + "ms"); + getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.heads().values().size()))); + }); + } catch (Exception ex) { + throw new RuntimeException(ex); + } } } diff --git a/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java b/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java index 286cfb7..8fcc7c7 100644 --- a/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java +++ b/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java @@ -1,18 +1,18 @@ package tsp.headdb.core.economy; import org.bukkit.entity.Player; +import tsp.helperlite.scheduler.promise.Promise; import java.math.BigDecimal; -import java.util.concurrent.CompletableFuture; public interface BasicEconomyProvider { - CompletableFuture canPurchase(Player player, BigDecimal cost); + Promise canPurchase(Player player, BigDecimal cost); - CompletableFuture withdraw(Player player, BigDecimal amount); + Promise withdraw(Player player, BigDecimal amount); - default CompletableFuture purchase(Player player, BigDecimal amount) { - return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false)); + default Promise purchase(Player player, BigDecimal amount) { + return canPurchase(player, amount).thenComposeAsync(result -> result ? withdraw(player, amount) : Promise.completed(false)); } void init(); diff --git a/src/main/java/tsp/headdb/core/economy/VaultProvider.java b/src/main/java/tsp/headdb/core/economy/VaultProvider.java index 0082445..76f03a7 100644 --- a/src/main/java/tsp/headdb/core/economy/VaultProvider.java +++ b/src/main/java/tsp/headdb/core/economy/VaultProvider.java @@ -5,24 +5,24 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; import tsp.headdb.HeadDB; +import tsp.helperlite.scheduler.promise.Promise; import java.math.BigDecimal; -import java.util.concurrent.CompletableFuture; public class VaultProvider implements BasicEconomyProvider { private Economy economy; @Override - public CompletableFuture canPurchase(Player player, BigDecimal cost) { + public Promise canPurchase(Player player, BigDecimal cost) { double effectiveCost = cost.doubleValue(); - return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0)); + return Promise.supplyingAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0)); } @Override - public CompletableFuture withdraw(Player player, BigDecimal amount) { + public Promise withdraw(Player player, BigDecimal amount) { double effectiveCost = amount.doubleValue(); - return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess()); + return Promise.supplyingAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess()); } diff --git a/src/main/java/tsp/headdb/core/storage/PlayerStorage.java b/src/main/java/tsp/headdb/core/storage/PlayerStorage.java index fa17684..155b8fe 100644 --- a/src/main/java/tsp/headdb/core/storage/PlayerStorage.java +++ b/src/main/java/tsp/headdb/core/storage/PlayerStorage.java @@ -44,7 +44,7 @@ public class PlayerStorage extends SerializableFileDataManager { - for (PlayerData entry : data) { + for (PlayerData entry : data.orElse(new HashSet<>())) { players.put(entry.uniqueId(), entry); } diff --git a/src/main/java/tsp/headdb/core/storage/Storage.java b/src/main/java/tsp/headdb/core/storage/Storage.java index ca5d775..719be00 100644 --- a/src/main/java/tsp/headdb/core/storage/Storage.java +++ b/src/main/java/tsp/headdb/core/storage/Storage.java @@ -1,6 +1,7 @@ package tsp.headdb.core.storage; import tsp.headdb.HeadDB; +import tsp.helperlite.Schedulers; import java.io.File; import java.util.concurrent.Executor; @@ -11,8 +12,8 @@ public class Storage { private final Executor executor; private final PlayerStorage playerStorage; - public Storage(int threads) { - executor = Executors.newFixedThreadPool(threads, HeadDBThreadFactory.FACTORY); + public Storage() { + executor = Schedulers.async(); validateDataDirectory(); playerStorage = new PlayerStorage(HeadDB.getInstance(), this); } diff --git a/src/main/java/tsp/headdb/core/task/UpdateTask.java b/src/main/java/tsp/headdb/core/task/UpdateTask.java index 774f287..cb9db86 100644 --- a/src/main/java/tsp/headdb/core/task/UpdateTask.java +++ b/src/main/java/tsp/headdb/core/task/UpdateTask.java @@ -4,51 +4,44 @@ import org.bukkit.Bukkit; import tsp.headdb.HeadDB; import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.events.AsyncHeadsFetchedEvent; +import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.head.Head; -import tsp.nexuslib.task.Task; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; -public class UpdateTask implements Task { - - private final long interval; - - public UpdateTask(long interval) { - this.interval = interval; - } +public class UpdateTask implements Runnable { @Override public void run() { - HeadAPI.getDatabase().update((time, heads) -> { - int size = 0; - for (List list : heads.values()) { - for (Head ignored : list) { - size++; - } - } - + HeadAPI.getDatabase().update().thenAcceptAsync(result -> { + HeadDB instance = HeadDB.getInstance(); String providerName = HeadAPI.getDatabase().getRequester().getProvider().name(); - HeadDB.getInstance().getLog().debug("Fetched: " + size + " Heads | Provider: " + providerName + " | Time: " + time + "ms (" + TimeUnit.MILLISECONDS.toSeconds(time) + "s)"); + instance.getLog().debug("Fetched: " + getHeadsCount(result.heads()) + " Heads | Provider: " + providerName + " | Time: " + result.elapsed() + "ms (" + TimeUnit.MILLISECONDS.toSeconds(result.elapsed()) + "s)"); Bukkit.getPluginManager().callEvent( new AsyncHeadsFetchedEvent( - heads, + result.heads(), providerName, - time)); + result.elapsed())); + + instance.getStorage().getPlayerStorage().backup(); + instance.getUpdateTask().ifPresentOrElse(task -> { + instance.getLog().debug("UpdateTask completed! Times ran: " + task.getTimesRan()); + }, () -> instance.getLog().debug("Initial UpdateTask completed!")); }); - HeadDB.getInstance().getStorage().getPlayerStorage().backup(); - HeadDB.getInstance().getLog().debug("UpdateTask finished!"); } - @Override - public long getRepeatInterval() { - return interval; - } + private int getHeadsCount(Map> heads) { + int n = 0; + for (List list : heads.values()) { + for (int i = 0; i < list.size(); i++) { + n++; + } + } - @Override - public boolean isAsync() { - return true; + return n; } } diff --git a/src/main/java/tsp/headdb/core/util/HeadDBLogger.java b/src/main/java/tsp/headdb/core/util/HeadDBLogger.java index f8931db..164cb36 100644 --- a/src/main/java/tsp/headdb/core/util/HeadDBLogger.java +++ b/src/main/java/tsp/headdb/core/util/HeadDBLogger.java @@ -1,63 +1,12 @@ package tsp.headdb.core.util; -import org.bukkit.Bukkit; -import tsp.nexuslib.util.StringUtils; +import tsp.nexuslib.logger.NexusLogger; @SuppressWarnings("unused") -public class HeadDBLogger { - - private final boolean debug; +public class HeadDBLogger extends NexusLogger { public HeadDBLogger(boolean debug) { - this.debug = debug; - } - public void info(String message) { - this.log(LogLevel.INFO, message); - } - - public void warning(String message) { - this.log(LogLevel.WARNING, message); - } - - public void error(String message) { - this.log(LogLevel.ERROR, message); - } - - public void debug(String message) { - this.log(LogLevel.DEBUG, message); - } - - public void trace(String message) { - this.log(LogLevel.TRACE, message); - } - - public void log(LogLevel level, String message) { - if ((level == LogLevel.DEBUG || level == LogLevel.TRACE) && !debug) { - return; - } - Bukkit.getConsoleSender().sendMessage(StringUtils.colorize("&cHeadDB &8>> " + level.getColor() + "[" + level.name() + "]: " + message)); - } - - public boolean isDebug() { - return this.debug; - } - - public enum LogLevel { - INFO, - WARNING, - ERROR, - DEBUG, - TRACE; - - public String getColor() { - return switch (this) { - case INFO -> "\u001b[32m"; - case WARNING -> "\u001b[33m"; - case ERROR -> "\u001b[31m"; - case DEBUG -> "\u001b[36m"; - case TRACE -> "\u001b[35m"; - }; - } + super("HeadDB", debug); } } diff --git a/src/main/java/tsp/headdb/core/util/Utils.java b/src/main/java/tsp/headdb/core/util/Utils.java index 19bd42e..a742922 100644 --- a/src/main/java/tsp/headdb/core/util/Utils.java +++ b/src/main/java/tsp/headdb/core/util/Utils.java @@ -10,15 +10,22 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.profile.PlayerProfile; +import org.bukkit.profile.PlayerTextures; import tsp.headdb.HeadDB; import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.economy.BasicEconomyProvider; import tsp.headdb.core.hook.Hooks; import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.head.Head; +import tsp.helperlite.scheduler.promise.Promise; +import tsp.nexuslib.builder.ItemBuilder; import tsp.nexuslib.inventory.Button; import tsp.nexuslib.inventory.PagedPane; import tsp.nexuslib.inventory.Pane; +import tsp.nexuslib.localization.TranslatableLocalization; +import tsp.nexuslib.server.ServerVersion; import tsp.nexuslib.util.StringUtils; import tsp.nexuslib.util.Validate; @@ -27,8 +34,9 @@ import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.lang.reflect.Field; import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; import java.util.*; -import java.util.concurrent.CompletableFuture; public class Utils { @@ -108,31 +116,36 @@ public class Utils { } public static void openFavoritesMenu(Player player) { - List heads = HeadAPI.getFavoriteHeads(player.getUniqueId()); - PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites")); - for (Head head : heads) { - main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> { - if (!player.hasPermission("headdb.favorites")) { - HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites"); - return; + try (Promise> promise = HeadAPI.getFavoriteHeads(player.getUniqueId())) { + promise.thenAcceptSync(heads -> { + PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites")); + for (Head head : heads) { + main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> { + if (!player.hasPermission("headdb.favorites")) { + HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites"); + return; + } + + if (fe.isLeftClick()) { + int amount = 1; + if (fe.isShiftClick()) { + amount = 64; + } + + Utils.purchase(player, head, amount); + } else if (fe.isRightClick()) { + HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture()); + HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName())); + openFavoritesMenu(player); + } + })); } - if (fe.isLeftClick()) { - int amount = 1; - if (fe.isShiftClick()) { - amount = 64; - } - - Utils.purchase(player, head, amount); - } else if (fe.isRightClick()) { - HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture()); - HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName())); - openFavoritesMenu(player); - } - })); + main.open(player); + }); + } catch (Exception ex) { + ex.printStackTrace(); } - - main.open(player); } @ParametersAreNonnullByDefault @@ -166,10 +179,10 @@ public class Utils { } } - private static CompletableFuture processPayment(Player player, Head head, int amount) { + private static Promise processPayment(Player player, Head head, int amount) { Optional optional = HeadDB.getInstance().getEconomyProvider(); if (optional.isEmpty()) { - return CompletableFuture.completedFuture(true); // No economy, the head is free + return Promise.completed(true); // No economy, the head is free } else { BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount); HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg @@ -177,8 +190,10 @@ public class Utils { .replace("%amount%", String.valueOf(amount)) .replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost)) ); - return optional.get().purchase(player, cost).thenApply(success -> { - Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> { + + try (Promise economyPromise = optional.get().purchase(player, cost)) { + // TODO: Might not need to be sync. + return economyPromise.thenApplySync((success) -> { if (success) { HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg .replace("%name%", head.getName()) @@ -186,46 +201,38 @@ public class Utils { } else { HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName())); } + return success; }); - return success; - - /* Note: Issues caused by sync call to async event but when run async above method fucks up. - Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), () -> { - HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success); - Bukkit.getPluginManager().callEvent(event); - }); - return true; - */ - }); + } catch (Exception ex) { + HeadDB.getInstance().getLog().severe("Failed to process payment: " + ex.getMessage()); + return Promise.exceptionally(ex); + } } } public static void purchase(Player player, Head head, int amount) { - processPayment(player, head, amount).whenComplete((success, ex) -> { - if (ex != null) { - HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName()); - ex.printStackTrace(); - } else { - // Bukkit API, therefore task is ran sync. - Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> { - if (success) { - ItemStack item = head.getItem(player.getUniqueId()); - item.setAmount(amount); - player.getInventory().addItem(item); - HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> { - if (command.isEmpty()) { - return; - } - if (Hooks.PAPI.enabled()) { - command = PlaceholderAPI.setPlaceholders(player, command); - } - - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); - }); + // Bukkit API - Has to be sync. + try (Promise paymentPromise = processPayment(player, head, amount)) { + paymentPromise.thenAcceptSync((success) -> { + if (success) { + ItemStack item = head.getItem(player.getUniqueId()); + item.setAmount(amount); + player.getInventory().addItem(item); + HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> { + if (command.isEmpty()) { + return; } + if (Hooks.PAPI.enabled()) { + command = PlaceholderAPI.setPlaceholders(player, command); + } + + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); }); - } - }); + } + }); + } catch (Exception ex) { + ex.printStackTrace(); + } } public static Optional getTexture(ItemStack head) { @@ -252,6 +259,51 @@ public class Utils { } } + public static ItemStack asItem(UUID receiver, Head head) { + TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); + ItemStack item = new ItemBuilder(Material.PLAYER_HEAD) + .name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + head.getName().toUpperCase(Locale.ROOT)).replace("%name%", head.getName())) + .setLore("&cID: " + head.getId(), "&7Tags: &e" + head.getTags()) + .build(); + + ItemMeta meta = item.getItemMeta(); + + // if version < 1.20.1 use reflection, else (1.20.2+) use PlayerProfile because spigot bitches otherwise. + if (ServerVersion.getVersion().orElse(ServerVersion.v_1_20_1).isOlderThan(ServerVersion.v_1_20_2)) { + try { + GameProfile profile = new GameProfile(head.getUniqueId(), head.getName()); + profile.getProperties().put("textures", new Property("textures", head.getTexture())); + + //noinspection DataFlowIssue + Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(meta, profile); + item.setItemMeta(meta); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { + //Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:"); + ex.printStackTrace(); + } + } else { + try { + PlayerProfile profile = Bukkit.createPlayerProfile(head.getUniqueId(), head.getName()); + PlayerTextures textures = profile.getTextures(); + String url = new String(Base64.getDecoder().decode(head.getTexture())); + textures.setSkin(new URL(url.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), url.length() - "\"}}}".length()))); + profile.setTextures(textures); + + SkullMeta skullMeta = (SkullMeta) meta; + if (skullMeta != null) { + skullMeta.setOwnerProfile(profile); + } + item.setItemMeta(skullMeta); + } catch (MalformedURLException ex) { + ex.printStackTrace(); + } + } + + return item; + } + public static int resolveInt(String raw) { try { return Integer.parseInt(raw); diff --git a/src/main/java/tsp/headdb/implementation/head/Head.java b/src/main/java/tsp/headdb/implementation/head/Head.java index eb6f356..b049116 100644 --- a/src/main/java/tsp/headdb/implementation/head/Head.java +++ b/src/main/java/tsp/headdb/implementation/head/Head.java @@ -1,19 +1,11 @@ package tsp.headdb.implementation.head; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import tsp.headdb.HeadDB; +import tsp.headdb.core.util.Utils; import tsp.headdb.implementation.category.Category; -import tsp.nexuslib.builder.ItemBuilder; -import tsp.nexuslib.localization.TranslatableLocalization; import tsp.nexuslib.util.Validate; import javax.annotation.ParametersAreNonnullByDefault; -import java.lang.reflect.Field; -import java.util.Locale; import java.util.UUID; public class Head { @@ -47,26 +39,7 @@ public class Head { public ItemStack getItem(UUID receiver) { if (item == null) { - TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); - item = new ItemBuilder(Material.PLAYER_HEAD) - .name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + name.toUpperCase(Locale.ROOT)).replace("%name%", name)) - .setLore("&cID: " + id, "&7Tags: &e" + tags) - .build(); - - ItemMeta meta = item.getItemMeta(); - GameProfile profile = new GameProfile(uniqueId, name); - profile.getProperties().put("textures", new Property("textures", texture)); - try { - //noinspection DataFlowIssue - Field profileField = meta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(meta, profile); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { - //Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:"); - ex.printStackTrace(); - } - - item.setItemMeta(meta); + item = Utils.asItem(receiver, this); } return item.clone(); // Return clone that changes are not reflected diff --git a/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java index 3f488ad..607bd97 100644 --- a/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java +++ b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java @@ -1,53 +1,59 @@ package tsp.headdb.implementation.head; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitScheduler; import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.requester.HeadProvider; import tsp.headdb.implementation.requester.Requester; +import tsp.helperlite.scheduler.promise.Promise; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; public class HeadDatabase { private final JavaPlugin plugin; - private final BukkitScheduler scheduler; private final Requester requester; private final ConcurrentHashMap> heads; private long timestamp; public HeadDatabase(JavaPlugin plugin, HeadProvider provider) { this.plugin = plugin; - this.scheduler = plugin.getServer().getScheduler(); this.requester = new Requester(plugin, provider); this.heads = new ConcurrentHashMap<>(); + + // Fill empty + for (Category cat : Category.VALUES) { + heads.put(cat, new ArrayList<>()); + } } public Map> getHeads() { return heads; } - public void getHeadsNoCache(BiConsumer>> heads) { - getScheduler().runTaskAsynchronously(plugin, () -> { + public Promise getHeadsNoCache() { + return Promise.supplyingAsync(() -> { long start = System.currentTimeMillis(); Map> result = new HashMap<>(); for (Category category : Category.VALUES) { - requester.fetchAndResolve(category, response -> result.put(category, response)); + result.put(category, requester.fetchAndResolve(category)); } - heads.accept(System.currentTimeMillis() - start, result); + return new HeadResult(System.currentTimeMillis() - start, result); }); } - public void update(BiConsumer>> fetched) { - getHeadsNoCache((elapsed, result) -> { - heads.putAll(result); - timestamp = System.currentTimeMillis(); - fetched.accept(elapsed, result); + public Promise update() { + return Promise.start() + .thenComposeAsync(compose -> getHeadsNoCache()) + .thenApplyAsync(result -> { + heads.clear(); + heads.putAll(result.heads()); + timestamp = System.currentTimeMillis(); + return result; }); } @@ -59,10 +65,6 @@ public class HeadDatabase { return plugin; } - public BukkitScheduler getScheduler() { - return scheduler; - } - public Requester getRequester() { return requester; } diff --git a/src/main/java/tsp/headdb/implementation/head/HeadResult.java b/src/main/java/tsp/headdb/implementation/head/HeadResult.java new file mode 100644 index 0000000..32ebd0b --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/head/HeadResult.java @@ -0,0 +1,11 @@ +package tsp.headdb.implementation.head; + +import tsp.headdb.implementation.category.Category; + +import java.util.List; +import java.util.Map; + +/** + * @author TheSilentPro (Silent) + */ +public record HeadResult(long elapsed, Map> heads) {} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/implementation/requester/Requester.java b/src/main/java/tsp/headdb/implementation/requester/Requester.java index 5d0075a..a8ad096 100644 --- a/src/main/java/tsp/headdb/implementation/requester/Requester.java +++ b/src/main/java/tsp/headdb/implementation/requester/Requester.java @@ -18,8 +18,14 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.function.Consumer; +/** + * Responsible for requesting heads from providers. + * + * @author TheSilentPro + * @see tsp.headdb.core.api.HeadAPI + * @see tsp.headdb.implementation.head.HeadDatabase + */ public class Requester { private final JavaPlugin plugin; @@ -30,51 +36,52 @@ public class Requester { this.provider = provider; } - public void fetchAndResolve(Category category, Consumer> heads) { + public List fetchAndResolve(Category category) { try { - fetch(category, response -> { - List result = new ArrayList<>(); - if (response.code() != 200) { - heads.accept(result); - return; - } + Response response = fetch(category); + List result = new ArrayList<>(); + if (response.code() != 200) { + return result; + } - JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray(); - for (JsonElement entry : main) { - JsonObject obj = entry.getAsJsonObject(); - int id = obj.get("id").getAsInt(); + JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray(); + for (JsonElement entry : main) { + JsonObject obj = entry.getAsJsonObject(); + int id = obj.get("id").getAsInt(); - if (plugin.getConfig().contains("blockedHeads.ids")) { - List blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids"); - if (blockedIds.contains(id)) { - HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")"); - continue; - } + if (plugin.getConfig().contains("blockedHeads.ids")) { + List blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids"); + if (blockedIds.contains(id)) { + HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")"); + continue; } - - result.add(new Head( - id, - Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()), - obj.get("name").getAsString(), - obj.get("value").getAsString(), - obj.get("tags").getAsString(), - response.date(), - category - )); } - heads.accept(result); - }); + result.add(new Head( + id, + Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()), + obj.get("name").getAsString(), + obj.get("value").getAsString(), + obj.get("tags").getAsString(), + response.date(), + category + )); + } + + return result; } catch (IOException ex) { HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name()); if (HeadDB.getInstance().getConfig().getBoolean("fallback") && provider != HeadProvider.HEAD_ARCHIVE) { // prevent recursion. Maybe switch to an attempts counter down in the future provider = HeadProvider.HEAD_ARCHIVE; - fetchAndResolve(category, heads); + return fetchAndResolve(category); + } else { + HeadDB.getInstance().getLog().error("Could not fetch heads from any provider!"); + return new ArrayList<>(); } } } - public void fetch(Category category, Consumer response) throws IOException { + public Response fetch(Category category) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); @@ -88,10 +95,9 @@ public class Requester { builder.append(line); } - response.accept(new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date"))); + connection.disconnect(); + return new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date")); } - - connection.disconnect(); } public HeadProvider getProvider() { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c83b94c..eea01be 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -90,10 +90,5 @@ blockedHeads: ids: - -1 -# Storage Options -storage: - # Amount of threads in the executor pool used for storage. - threads: 2 - # Debug Mode debug: false \ No newline at end of file