3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-26 02:50:07 +01:00

lots of improvements and bugfixes.

Dieser Commit ist enthalten in:
Silent 2023-11-14 23:28:56 +01:00
Ursprung 7ef5e30694
Commit e4691102ae
18 geänderte Dateien mit 312 neuen und 285 gelöschten Zeilen

22
pom.xml
Datei anzeigen

@ -6,7 +6,7 @@
<groupId>tsp.headdb</groupId> <groupId>tsp.headdb</groupId>
<artifactId>HeadDB</artifactId> <artifactId>HeadDB</artifactId>
<version>5.0.0-rc.7</version> <version>5.0.0-rc.8</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HeadDB</name> <name>HeadDB</name>
@ -55,13 +55,13 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version> <version>1.20.2-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>com.mojang</groupId>
<artifactId>authlib</artifactId> <artifactId>authlib</artifactId>
<version>1.5.21</version> <version>4.0.43</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -75,20 +75,26 @@
<dependency> <dependency>
<groupId>com.github.TheSilentPro</groupId> <groupId>com.github.TheSilentPro</groupId>
<artifactId>NexusLib</artifactId> <artifactId>NexusLib</artifactId>
<version>c01a0a0a7d</version> <version>9d8b16b770</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.TheSilentPro</groupId> <groupId>com.github.TheSilentPro</groupId>
<artifactId>Warehouse</artifactId> <artifactId>Warehouse</artifactId>
<version>882b42fc75</version> <version>b228e4f8b1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.wesjd</groupId> <groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId> <artifactId>anvilgui</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.9.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>HelperLite</artifactId>
<version>775582f23b</version>
</dependency> </dependency>
<!-- Soft Dependencies --> <!-- Soft Dependencies -->
<!--suppress VulnerableLibrariesLocal -->
<dependency> <dependency>
<groupId>com.github.MilkBowl</groupId> <groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId> <artifactId>VaultAPI</artifactId>
@ -98,7 +104,7 @@
<dependency> <dependency>
<groupId>me.clip</groupId> <groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId> <artifactId>placeholderapi</artifactId>
<version>2.11.1</version> <version>2.11.5</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@ -183,7 +189,7 @@
<additionalDependency> <additionalDependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version> <version>1.20.2-R0.1-SNAPSHOT</version>
</additionalDependency> </additionalDependency>
</additionalDependencies> </additionalDependencies>
</configuration> </configuration>

Datei anzeigen

@ -7,17 +7,25 @@ import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.core.storage.Storage; import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask; import tsp.headdb.core.task.UpdateTask;
import tsp.headdb.core.util.HeadDBLogger; 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.NexusPlugin;
import tsp.nexuslib.inventory.PaneListener; import tsp.nexuslib.inventory.PaneListener;
import tsp.nexuslib.localization.TranslatableLocalization; import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.util.PluginUtils;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class HeadDB extends NexusPlugin { public class HeadDB extends NexusPlugin {
@ -27,22 +35,27 @@ public class HeadDB extends NexusPlugin {
private Storage storage; private Storage storage;
private BasicEconomyProvider economyProvider; private BasicEconomyProvider economyProvider;
private CommandManager commandManager; private CommandManager commandManager;
private Task updateTask;
@Override @Override
public void onStart(NexusPlugin nexusPlugin) { public void onStart(NexusPlugin nexusPlugin) {
instance = this; instance = this;
HelperLite.init(this);
instance.saveDefaultConfig(); instance.saveDefaultConfig();
instance.logger = new HeadDBLogger(getConfig().getBoolean("debug")); instance.logger = new HeadDBLogger(getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion()); instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
instance.logger.info("Loaded " + loadLocalization() + " languages!"); instance.logger.info("Loaded " + loadLocalization() + " languages!");
instance.initStorage(); instance.initStorage();
instance.initEconomy(); instance.initEconomy();
startUpdateTask();
new PaneListener(this); new PaneListener(this);
// TODO: Commands helperlite
instance.commandManager = new CommandManager(); instance.commandManager = new CommandManager();
loadCommands(); 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() { private void initMetrics() {
Metrics metrics = new Metrics(this, 9152); 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() { private void initStorage() {
storage = new Storage(getConfig().getInt("storage.threads")); storage = new Storage();
storage.getPlayerStorage().init(); storage.getPlayerStorage().init();
} }
@ -156,6 +188,10 @@ public class HeadDB extends NexusPlugin {
// Getters // Getters
public Optional<Task> getUpdateTask() {
return Optional.ofNullable(updateTask);
}
public Storage getStorage() { public Storage getStorage() {
return storage; return storage;
} }

Datei anzeigen

@ -5,6 +5,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.core.storage.HeadDBThreadFactory;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import java.io.*; import java.io.*;
@ -132,8 +133,7 @@ class Metrics {
/** The version of the Metrics class. */ /** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.0"; public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler = private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(HeadDBThreadFactory.FACTORY);
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";

Datei anzeigen

@ -9,6 +9,7 @@ import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.HeadDatabase; import tsp.headdb.implementation.head.HeadDatabase;
import tsp.headdb.implementation.head.LocalHead; import tsp.headdb.implementation.head.LocalHead;
import tsp.headdb.implementation.requester.HeadProvider; import tsp.headdb.implementation.requester.HeadProvider;
import tsp.helperlite.scheduler.promise.Promise;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.*; import java.util.*;
@ -163,14 +164,16 @@ public final class HeadAPI {
* @return {@link Set<Head> Favorite Heads} * @return {@link Set<Head> Favorite Heads}
*/ */
@Nonnull @Nonnull
public static List<Head> getFavoriteHeads(UUID player) { public static Promise<List<Head>> getFavoriteHeads(UUID player) {
List<Head> result = new ArrayList<>(); return Promise.supplyingAsync(() -> {
Optional<PlayerData> data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player); List<Head> result = new ArrayList<>();
data.ifPresent(playerData -> playerData.favorites() Optional<PlayerData> data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player);
.forEach(texture -> getHeadByTexture(texture) data.ifPresent(playerData -> playerData.favorites()
.ifPresent(result::add)) .forEach(texture -> getHeadByTexture(texture)
); .ifPresent(result::add))
return result; );
return result;
});
} }
/** /**

Datei anzeigen

@ -1,8 +1,6 @@
package tsp.headdb.core.command; package tsp.headdb.core.command;
import java.util.Arrays;
import net.wesjd.anvilgui.AnvilGUI; import net.wesjd.anvilgui.AnvilGUI;
import net.wesjd.anvilgui.AnvilGUI.Builder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -24,10 +22,7 @@ import tsp.nexuslib.util.StringUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter { public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {

Datei anzeigen

@ -1,9 +1,10 @@
package tsp.headdb.core.command; package tsp.headdb.core.command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.implementation.head.HeadResult;
import tsp.helperlite.scheduler.promise.Promise;
public class CommandUpdate extends SubCommand { public class CommandUpdate extends SubCommand {
@ -14,10 +15,14 @@ public class CommandUpdate extends SubCommand {
@Override @Override
public void handle(CommandSender sender, String[] args) { public void handle(CommandSender sender, String[] args) {
getLocalization().sendMessage(sender, "updateDatabase"); getLocalization().sendMessage(sender, "updateDatabase");
HeadAPI.getDatabase().update((time, result) -> { try (Promise<HeadResult> promise = HeadAPI.getDatabase().update()) {
HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms"); promise.thenAcceptSync(result -> {
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.values().size())))); 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);
}
} }
} }

Datei anzeigen

@ -1,18 +1,18 @@
package tsp.headdb.core.economy; package tsp.headdb.core.economy;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import tsp.helperlite.scheduler.promise.Promise;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public interface BasicEconomyProvider { public interface BasicEconomyProvider {
CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost); Promise<Boolean> canPurchase(Player player, BigDecimal cost);
CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount); Promise<Boolean> withdraw(Player player, BigDecimal amount);
default CompletableFuture<Boolean> purchase(Player player, BigDecimal amount) { default Promise<Boolean> purchase(Player player, BigDecimal amount) {
return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false)); return canPurchase(player, amount).thenComposeAsync(result -> result ? withdraw(player, amount) : Promise.completed(false));
} }
void init(); void init();

Datei anzeigen

@ -5,24 +5,24 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.helperlite.scheduler.promise.Promise;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public class VaultProvider implements BasicEconomyProvider { public class VaultProvider implements BasicEconomyProvider {
private Economy economy; private Economy economy;
@Override @Override
public CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost) { public Promise<Boolean> canPurchase(Player player, BigDecimal cost) {
double effectiveCost = cost.doubleValue(); 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 @Override
public CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount) { public Promise<Boolean> withdraw(Player player, BigDecimal amount) {
double effectiveCost = amount.doubleValue(); 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());
} }

Datei anzeigen

@ -44,7 +44,7 @@ public class PlayerStorage extends SerializableFileDataManager<HashSet<PlayerDat
public void init() { public void init() {
load().whenComplete((data, ex) -> { load().whenComplete((data, ex) -> {
for (PlayerData entry : data) { for (PlayerData entry : data.orElse(new HashSet<>())) {
players.put(entry.uniqueId(), entry); players.put(entry.uniqueId(), entry);
} }

Datei anzeigen

@ -1,6 +1,7 @@
package tsp.headdb.core.storage; package tsp.headdb.core.storage;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.helperlite.Schedulers;
import java.io.File; import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -11,8 +12,8 @@ public class Storage {
private final Executor executor; private final Executor executor;
private final PlayerStorage playerStorage; private final PlayerStorage playerStorage;
public Storage(int threads) { public Storage() {
executor = Executors.newFixedThreadPool(threads, HeadDBThreadFactory.FACTORY); executor = Schedulers.async();
validateDataDirectory(); validateDataDirectory();
playerStorage = new PlayerStorage(HeadDB.getInstance(), this); playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
} }

Datei anzeigen

@ -4,51 +4,44 @@ import org.bukkit.Bukkit;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.api.events.AsyncHeadsFetchedEvent; import tsp.headdb.core.api.events.AsyncHeadsFetchedEvent;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.Head;
import tsp.nexuslib.task.Task;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class UpdateTask implements Task { public class UpdateTask implements Runnable {
private final long interval;
public UpdateTask(long interval) {
this.interval = interval;
}
@Override @Override
public void run() { public void run() {
HeadAPI.getDatabase().update((time, heads) -> { HeadAPI.getDatabase().update().thenAcceptAsync(result -> {
int size = 0; HeadDB instance = HeadDB.getInstance();
for (List<Head> list : heads.values()) {
for (Head ignored : list) {
size++;
}
}
String providerName = HeadAPI.getDatabase().getRequester().getProvider().name(); 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( Bukkit.getPluginManager().callEvent(
new AsyncHeadsFetchedEvent( new AsyncHeadsFetchedEvent(
heads, result.heads(),
providerName, 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 private int getHeadsCount(Map<Category, List<Head>> heads) {
public long getRepeatInterval() { int n = 0;
return interval; for (List<Head> list : heads.values()) {
} for (int i = 0; i < list.size(); i++) {
n++;
}
}
@Override return n;
public boolean isAsync() {
return true;
} }
} }

Datei anzeigen

@ -1,63 +1,12 @@
package tsp.headdb.core.util; package tsp.headdb.core.util;
import org.bukkit.Bukkit; import tsp.nexuslib.logger.NexusLogger;
import tsp.nexuslib.util.StringUtils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class HeadDBLogger { public class HeadDBLogger extends NexusLogger {
private final boolean debug;
public HeadDBLogger(boolean debug) { public HeadDBLogger(boolean debug) {
this.debug = debug; super("HeadDB", 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";
};
}
} }
} }

Datei anzeigen

@ -10,15 +10,22 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; 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.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.economy.BasicEconomyProvider; import tsp.headdb.core.economy.BasicEconomyProvider;
import tsp.headdb.core.hook.Hooks; import tsp.headdb.core.hook.Hooks;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; 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.Button;
import tsp.nexuslib.inventory.PagedPane; import tsp.nexuslib.inventory.PagedPane;
import tsp.nexuslib.inventory.Pane; import tsp.nexuslib.inventory.Pane;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.server.ServerVersion;
import tsp.nexuslib.util.StringUtils; import tsp.nexuslib.util.StringUtils;
import tsp.nexuslib.util.Validate; import tsp.nexuslib.util.Validate;
@ -27,8 +34,9 @@ import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
public class Utils { public class Utils {
@ -108,31 +116,36 @@ public class Utils {
} }
public static void openFavoritesMenu(Player player) { public static void openFavoritesMenu(Player player) {
List<Head> heads = HeadAPI.getFavoriteHeads(player.getUniqueId()); try (Promise<List<Head>> promise = 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")); promise.thenAcceptSync(heads -> {
for (Head head : heads) { PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites"));
main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> { for (Head head : heads) {
if (!player.hasPermission("headdb.favorites")) { main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites"); if (!player.hasPermission("headdb.favorites")) {
return; 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()) { main.open(player);
int amount = 1; });
if (fe.isShiftClick()) { } catch (Exception ex) {
amount = 64; ex.printStackTrace();
}
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);
} }
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
@ -166,10 +179,10 @@ public class Utils {
} }
} }
private static CompletableFuture<Boolean> processPayment(Player player, Head head, int amount) { private static Promise<Boolean> processPayment(Player player, Head head, int amount) {
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider(); Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
if (optional.isEmpty()) { if (optional.isEmpty()) {
return CompletableFuture.completedFuture(true); // No economy, the head is free return Promise.completed(true); // No economy, the head is free
} else { } else {
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount); BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount);
HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg
@ -177,8 +190,10 @@ public class Utils {
.replace("%amount%", String.valueOf(amount)) .replace("%amount%", String.valueOf(amount))
.replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost)) .replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost))
); );
return optional.get().purchase(player, cost).thenApply(success -> {
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> { try (Promise<Boolean> economyPromise = optional.get().purchase(player, cost)) {
// TODO: Might not need to be sync.
return economyPromise.thenApplySync((success) -> {
if (success) { if (success) {
HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg
.replace("%name%", head.getName()) .replace("%name%", head.getName())
@ -186,46 +201,38 @@ public class Utils {
} else { } else {
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName())); HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName()));
} }
return success;
}); });
return success; } catch (Exception ex) {
HeadDB.getInstance().getLog().severe("Failed to process payment: " + ex.getMessage());
/* Note: Issues caused by sync call to async event but when run async above method fucks up. return Promise.exceptionally(ex);
Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), () -> { }
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
Bukkit.getPluginManager().callEvent(event);
});
return true;
*/
});
} }
} }
public static void purchase(Player player, Head head, int amount) { public static void purchase(Player player, Head head, int amount) {
processPayment(player, head, amount).whenComplete((success, ex) -> { // Bukkit API - Has to be sync.
if (ex != null) { try (Promise<Boolean> paymentPromise = processPayment(player, head, amount)) {
HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName()); paymentPromise.thenAcceptSync((success) -> {
ex.printStackTrace(); if (success) {
} else { ItemStack item = head.getItem(player.getUniqueId());
// Bukkit API, therefore task is ran sync. item.setAmount(amount);
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> { player.getInventory().addItem(item);
if (success) { HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
ItemStack item = head.getItem(player.getUniqueId()); if (command.isEmpty()) {
item.setAmount(amount); return;
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);
});
} }
if (Hooks.PAPI.enabled()) {
command = PlaceholderAPI.setPlaceholders(player, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}); });
} }
}); });
} catch (Exception ex) {
ex.printStackTrace();
}
} }
public static Optional<String> getTexture(ItemStack head) { public static Optional<String> 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) { public static int resolveInt(String raw) {
try { try {
return Integer.parseInt(raw); return Integer.parseInt(raw);

Datei anzeigen

@ -1,19 +1,11 @@
package tsp.headdb.implementation.head; 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.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import tsp.headdb.core.util.Utils;
import tsp.headdb.HeadDB;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.util.Validate; import tsp.nexuslib.util.Validate;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.UUID; import java.util.UUID;
public class Head { public class Head {
@ -47,26 +39,7 @@ public class Head {
public ItemStack getItem(UUID receiver) { public ItemStack getItem(UUID receiver) {
if (item == null) { if (item == null) {
TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); item = Utils.asItem(receiver, this);
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);
} }
return item.clone(); // Return clone that changes are not reflected return item.clone(); // Return clone that changes are not reflected

Datei anzeigen

@ -1,53 +1,59 @@
package tsp.headdb.implementation.head; package tsp.headdb.implementation.head;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.requester.HeadProvider; import tsp.headdb.implementation.requester.HeadProvider;
import tsp.headdb.implementation.requester.Requester; import tsp.headdb.implementation.requester.Requester;
import tsp.helperlite.scheduler.promise.Promise;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
public class HeadDatabase { public class HeadDatabase {
private final JavaPlugin plugin; private final JavaPlugin plugin;
private final BukkitScheduler scheduler;
private final Requester requester; private final Requester requester;
private final ConcurrentHashMap<Category, List<Head>> heads; private final ConcurrentHashMap<Category, List<Head>> heads;
private long timestamp; private long timestamp;
public HeadDatabase(JavaPlugin plugin, HeadProvider provider) { public HeadDatabase(JavaPlugin plugin, HeadProvider provider) {
this.plugin = plugin; this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
this.requester = new Requester(plugin, provider); this.requester = new Requester(plugin, provider);
this.heads = new ConcurrentHashMap<>(); this.heads = new ConcurrentHashMap<>();
// Fill empty
for (Category cat : Category.VALUES) {
heads.put(cat, new ArrayList<>());
}
} }
public Map<Category, List<Head>> getHeads() { public Map<Category, List<Head>> getHeads() {
return heads; return heads;
} }
public void getHeadsNoCache(BiConsumer<Long, Map<Category, List<Head>>> heads) { public Promise<HeadResult> getHeadsNoCache() {
getScheduler().runTaskAsynchronously(plugin, () -> { return Promise.supplyingAsync(() -> {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
Map<Category, List<Head>> result = new HashMap<>(); Map<Category, List<Head>> result = new HashMap<>();
for (Category category : Category.VALUES) { 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<Long, Map<Category, List<Head>>> fetched) { public Promise<HeadResult> update() {
getHeadsNoCache((elapsed, result) -> { return Promise.start()
heads.putAll(result); .thenComposeAsync(compose -> getHeadsNoCache())
timestamp = System.currentTimeMillis(); .thenApplyAsync(result -> {
fetched.accept(elapsed, result); heads.clear();
heads.putAll(result.heads());
timestamp = System.currentTimeMillis();
return result;
}); });
} }
@ -59,10 +65,6 @@ public class HeadDatabase {
return plugin; return plugin;
} }
public BukkitScheduler getScheduler() {
return scheduler;
}
public Requester getRequester() { public Requester getRequester() {
return requester; return requester;
} }

Datei anzeigen

@ -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<Category, List<Head>> heads) {}

Datei anzeigen

@ -18,8 +18,14 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; 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 { public class Requester {
private final JavaPlugin plugin; private final JavaPlugin plugin;
@ -30,51 +36,52 @@ public class Requester {
this.provider = provider; this.provider = provider;
} }
public void fetchAndResolve(Category category, Consumer<List<Head>> heads) { public List<Head> fetchAndResolve(Category category) {
try { try {
fetch(category, response -> { Response response = fetch(category);
List<Head> result = new ArrayList<>(); List<Head> result = new ArrayList<>();
if (response.code() != 200) { if (response.code() != 200) {
heads.accept(result); return result;
return; }
}
JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray(); JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray();
for (JsonElement entry : main) { for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject(); JsonObject obj = entry.getAsJsonObject();
int id = obj.get("id").getAsInt(); int id = obj.get("id").getAsInt();
if (plugin.getConfig().contains("blockedHeads.ids")) { if (plugin.getConfig().contains("blockedHeads.ids")) {
List<Integer> blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids"); List<Integer> blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids");
if (blockedIds.contains(id)) { if (blockedIds.contains(id)) {
HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")"); HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")");
continue; 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) { } catch (IOException ex) {
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name()); 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 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; 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> response) throws IOException { public Response fetch(Category category) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();
connection.setConnectTimeout(5000); connection.setConnectTimeout(5000);
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
@ -88,10 +95,9 @@ public class Requester {
builder.append(line); 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() { public HeadProvider getProvider() {

Datei anzeigen

@ -90,10 +90,5 @@ blockedHeads:
ids: ids:
- -1 - -1
# Storage Options
storage:
# Amount of threads in the executor pool used for storage.
threads: 2
# Debug Mode # Debug Mode
debug: false debug: false