Make our bukkit events async

Also, #76 store signs in instance
This commit is contained in:
ME1312 2022-04-05 19:26:50 -04:00
parent 7d9de9bf6e
commit e4300e76e2
No known key found for this signature in database
GPG Key ID: FEFFE2F698E88FA8
61 changed files with 673 additions and 1544 deletions

View File

@ -11,7 +11,7 @@ Paste in the version information from the SubServers app in question here. To ge
Here you can write about what happened that shouldn't have. If you have any errors in your console related to what happened, you should also paste those here.
### How It Happened
Tell us step-by-step how to recreate the problem. This step is vital for us to determine whether or not the problem happens to everyone else too.
Tell us step-by-step how to recreate the problem. This step is vital for us to determine whether the problem happens to everyone else too.
### Additional Information
Here you can provide any extra details you may think useful for solving the problem.

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2015-2021 ME1312
Copyright (C) 2015-2022 ME1312
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,6 +1,7 @@
package net.ME1312.SubServers.Sync.Library;
package net.ME1312.SubServers.Bungee.Library;
import net.ME1312.SubServers.Sync.SubAPI;
import net.ME1312.SubServers.Bungee.BungeeAPI;
import net.ME1312.SubServers.Bungee.BungeeCommon;
import gnu.trove.map.hash.TIntObjectHashMap;
import net.md_5.bungee.api.ProxyServer;
@ -29,6 +30,9 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
* SubServers BStats Metrics Implementation
*/
public class Metrics {
private final Plugin plugin;
@ -79,7 +83,6 @@ public class Metrics {
logResponseStatusText);
}
/** Loads the bStats configuration. */
private void loadConfig() throws IOException {
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
@ -118,15 +121,6 @@ public class Metrics {
}
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private static final AdvancedPie PLAYER_VERSIONS;
static {
final int[] PROTOCOL_VERSIONS;
@ -169,20 +163,26 @@ public class Metrics {
});
}
public void appendAppData() {
addCustomChart(PLAYER_VERSIONS);
/**
* Add subservers platform information as custom charts
*/
public Metrics addPlatformCharts() {
return addCustomChart(new SimplePie("subservers_version", () -> BungeeAPI.getInstance().getWrapperVersion().toString())).addCustomChart(PLAYER_VERSIONS);
}
public void appendPluginData() {
addCustomChart(new SimplePie("subservers_version", () -> {
return SubAPI.getInstance().getWrapperVersion().toString();
}));
addCustomChart(PLAYER_VERSIONS);
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public Metrics addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
return this;
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", plugin.getProxy().getOnlineCount());
builder.appendField("managedServers", plugin.getProxy().getServers().size());
builder.appendField("managedServers", ((BungeeCommon) plugin.getProxy()).getServersCopy().size());
builder.appendField("onlineMode", plugin.getProxy().getConfig().isOnlineMode() ? 1 : 0);
builder.appendField("bungeecordVersion", plugin.getProxy().getVersion());
builder.appendField("javaVersion", System.getProperty("java.version"));
@ -199,7 +199,7 @@ public class Metrics {
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "2.2.1";
public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
@ -284,6 +284,7 @@ public class Metrics {
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from bStats
startSubmitting();
}
}
@ -420,9 +421,9 @@ public class Metrics {
}
}
public static class AdvancedBarChart extends CustomChart {
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, int[]>> callable;
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
@ -430,99 +431,33 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
@ -570,6 +505,76 @@ public class Metrics {
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
@ -604,32 +609,6 @@ public class Metrics {
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
@ -656,9 +635,9 @@ public class Metrics {
}
}
public static class DrilldownPie extends CustomChart {
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
@ -666,33 +645,29 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (reallyAllSkipped) {
if (allSkipped) {
// Null = skip the chart
return null;
}
@ -700,6 +675,32 @@ public class Metrics {
}
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*

View File

@ -34,7 +34,7 @@
<dependency>
<groupId>net.ME1312.SubData</groupId>
<artifactId>Server</artifactId>
<version>22w11a</version>
<version>22w11c</version>
<scope>compile</scope>
</dependency>
<dependency>

View File

@ -4,12 +4,16 @@ package net.ME1312.SubServers.Bungee.Library.Compatibility;
import net.ME1312.SubServers.Bungee.SubAPI;
import com.google.common.io.Resources;
import net.md_5.bungee.api.ProxyServer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Logger;
import static java.util.logging.Level.SEVERE;
/**
* JNA Library Loader Class
@ -29,12 +33,13 @@ public class JNA {
public static ClassLoader get() {
if (JNA == null) {
boolean announced = false;
Logger log = ProxyServer.getInstance().getLogger();
File library = new File(SubAPI.getInstance().getInternals().dir, "SubServers/Cache/Libraries");
File jna = new File(library, "jna-" + JNA_VERSION + ".jar");
jna.getParentFile().mkdirs();
if (!jna.exists()) {
announced = true;
System.out.println(">> Downloading JNA Library v" + JNA_VERSION);
log.info(">> Downloading JNA v" + JNA_VERSION);
try (FileOutputStream fin = new FileOutputStream(jna)) {
Resources.copy(new URL(JNA_DOWNLOAD.replace("$1", "jna")), fin);
} catch (Throwable e) {
@ -45,7 +50,7 @@ public class JNA {
File platform = new File(library, "jna-platform-" + JNA_VERSION + ".jar");
platform.getParentFile().mkdirs();
if (!platform.exists()) {
if (!announced) System.out.println(">> Downloading JNA Library v" + JNA_VERSION);
if (!announced) log.info(">> Downloading JNA platform v" + JNA_VERSION);
announced = true;
try (FileOutputStream fin = new FileOutputStream(platform)) {
Resources.copy(new URL(JNA_DOWNLOAD.replace("$1", "jna-platform")), fin);
@ -55,16 +60,14 @@ public class JNA {
}
}
if (jna.exists() && platform.exists()) {
if (announced) System.out.println(">> Loading JNA Library");
if (announced) log.info(">> JNA download complete");
try {
JNA = new URLClassLoader(new URL[]{jna.toURI().toURL(), platform.toURI().toURL()});
} catch (Throwable e) {
System.out.println(">> Could not load JNA Library:");
e.printStackTrace();
log.log(SEVERE, ">> Couldn't load JNA:", e);
}
} else {
System.out.println(">> Could not load JNA Library:");
new FileNotFoundException().printStackTrace();
log.log(SEVERE, ">> Couldn't load JNA:", new FileNotFoundException());
}
}
return JNA;

View File

@ -1,917 +0,0 @@
package net.ME1312.SubServers.Bungee.Library;
import net.ME1312.SubData.Server.DataServer;
import net.ME1312.SubServers.Bungee.BungeeCommon;
import net.ME1312.SubServers.Bungee.SubAPI;
import gnu.trove.map.hash.TIntObjectHashMap;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.protocol.ProtocolConstants;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
public class Metrics {
private final Plugin plugin;
private final MetricsBase metricsBase;
private boolean enabled;
private String serverUUID;
private boolean logErrors = false;
private boolean logSentData;
private boolean logResponseStatusText;
/**
* Creates a new Metrics instance.
*
* @param plugin Your plugin instance.
* @param serviceId The id of the service. It can be found at <a
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(Plugin plugin, int serviceId) {
this.plugin = plugin;
try {
loadConfig();
} catch (IOException e) {
// Failed to load configuration
plugin.getLogger().log(Level.WARNING, "Failed to load bStats config!", e);
metricsBase = null;
return;
}
metricsBase =
new MetricsBase(
"bungeecord",
serverUUID,
serviceId,
enabled,
this::appendPlatformData,
this::appendServiceData,
null,
() -> true,
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
(message) -> this.plugin.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText);
}
/** Loads the bStats configuration. */
private void loadConfig() throws IOException {
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
bStatsFolder.mkdirs();
File configFile = new File(bStatsFolder, "config.yml");
if (!configFile.exists()) {
writeFile(
configFile,
"# bStats (https://bStats.org) collects some basic information for plugin authors, like how",
"# many people use their plugin and their total player count. It's recommended to keep bStats",
"# enabled, but if you're not comfortable with this, you can turn this setting off. There is no",
"# performance penalty associated with having metrics enabled, and data sent to bStats is fully",
"# anonymous.",
"enabled: true",
"serverUuid: \"" + UUID.randomUUID() + "\"",
"logFailedRequests: false",
"logSentData: false",
"logResponseStatusText: false");
}
Configuration configuration =
ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
// Load configuration
enabled = configuration.getBoolean("enabled", true);
serverUUID = configuration.getString("serverUuid");
logErrors = configuration.getBoolean("logFailedRequests", false);
logSentData = configuration.getBoolean("logSentData", false);
logResponseStatusText = configuration.getBoolean("logResponseStatusText", false);
}
private void writeFile(File file, String... lines) throws IOException {
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) {
for (String line : lines) {
bufferedWriter.write(line);
bufferedWriter.newLine();
}
}
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private static final AdvancedPie PLAYER_VERSIONS;
static {
final int[] PROTOCOL_VERSIONS;
final String[] PROTOCOL_NAMES;
{
TIntObjectHashMap<String> protocols = new TIntObjectHashMap<String>();
try {
for (Field f : ProtocolConstants.class.getDeclaredFields()) {
int fm = f.getModifiers();
if (Modifier.isPublic(fm) && Modifier.isStatic(fm) && Modifier.isFinal(fm) && f.getType() == int.class && f.getName().startsWith("MINECRAFT_")) {
protocols.put(f.getInt(null), f.getName().substring(10).replace('_', '.'));
}
}
} catch (Throwable e) {
e.printStackTrace();
}
PROTOCOL_VERSIONS = protocols.keys();
PROTOCOL_NAMES = new String[PROTOCOL_VERSIONS.length];
Arrays.sort(PROTOCOL_VERSIONS);
for (int i = 0; i < PROTOCOL_VERSIONS.length; ++i) {
PROTOCOL_NAMES[i] = protocols.get(PROTOCOL_VERSIONS[i]);
}
}
PLAYER_VERSIONS = new AdvancedPie("player_versions", () -> {
int[] players = new int[PROTOCOL_VERSIONS.length];
for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) {
int i = Arrays.binarySearch(PROTOCOL_VERSIONS, player.getPendingConnection().getVersion());
if (i != -1) {
++players[i];
}
}
HashMap<String, Integer> map = new HashMap<String, Integer>();
for (int i = 0; i < PROTOCOL_NAMES.length; ++i) if (players[i] != 0) {
map.put(PROTOCOL_NAMES[i], players[i]);
}
return map;
});
}
public void appendAppData() {
addCustomChart(new SingleLineChart("managed_hosts", () -> {
return SubAPI.getInstance().getHosts().size();
}));
addCustomChart(new SingleLineChart("subdata_connected", () -> {
DataServer subdata = SubAPI.getInstance().getSubDataNetwork();
return (subdata != null)? subdata.getClients().size() : 0;
}));
addCustomChart(PLAYER_VERSIONS);
}
public void appendPluginData() {
addCustomChart(new SimplePie("subservers_version", () -> {
return SubAPI.getInstance().getWrapperVersion().toString();
}));
addCustomChart(PLAYER_VERSIONS);
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", plugin.getProxy().getOnlineCount());
builder.appendField("managedServers", ((BungeeCommon) plugin.getProxy()).getServersCopy().size());
builder.appendField("onlineMode", plugin.getProxy().getConfig().isOnlineMode() ? 1 : 0);
builder.appendField("bungeecordVersion", plugin.getProxy().getVersion());
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
}
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "2.2.1";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
private final String platform;
private final String serverUuid;
private final int serviceId;
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
private final Consumer<Runnable> submitTaskConsumer;
private final Supplier<Boolean> checkServiceEnabledSupplier;
private final BiConsumer<String, Throwable> errorLogger;
private final Consumer<String> infoLogger;
private final boolean logErrors;
private final boolean logSentData;
private final boolean logResponseStatusText;
private final Set<CustomChart> customCharts = new HashSet<>();
private final boolean enabled;
/**
* Creates a new MetricsBase class instance.
*
* @param platform The platform of the service.
* @param serviceId The id of the service.
* @param serverUuid The server uuid.
* @param enabled Whether or not data sending is enabled.
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all platform-specific data.
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all service-specific data.
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
* used to delegate the data collection to a another thread to prevent errors caused by
* concurrency. Can be {@code null}.
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
* @param errorLogger A consumer that accepts log message and an error.
* @param infoLogger A consumer that accepts info log messages.
* @param logErrors Whether or not errors should be logged.
* @param logSentData Whether or not the sent data should be logged.
* @param logResponseStatusText Whether or not the response status text should be logged.
*/
public MetricsBase(
String platform,
String serverUuid,
int serviceId,
boolean enabled,
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
Consumer<Runnable> submitTaskConsumer,
Supplier<Boolean> checkServiceEnabledSupplier,
BiConsumer<String, Throwable> errorLogger,
Consumer<String> infoLogger,
boolean logErrors,
boolean logSentData,
boolean logResponseStatusText) {
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
this.enabled = enabled;
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
this.appendServiceDataConsumer = appendServiceDataConsumer;
this.submitTaskConsumer = submitTaskConsumer;
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
this.errorLogger = errorLogger;
this.infoLogger = infoLogger;
this.logErrors = logErrors;
this.logSentData = logSentData;
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
startSubmitting();
}
}
public void addCustomChart(CustomChart chart) {
this.customCharts.add(chart);
}
private void startSubmitting() {
final Runnable submitTask =
() -> {
if (!enabled || !checkServiceEnabledSupplier.get()) {
// Submitting data or service is disabled
scheduler.shutdown();
return;
}
if (submitTaskConsumer != null) {
submitTaskConsumer.accept(this::submitData);
} else {
this.submitData();
}
};
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution
// of requests on the
// bStats backend. To circumvent this problem, we introduce some randomness into the initial
// and second delay.
// WARNING: You must not modify and part of this Metrics class, including the submit delay or
// frequency!
// WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it!
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(
submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
}
private void submitData() {
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
appendPlatformDataConsumer.accept(baseJsonBuilder);
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
appendServiceDataConsumer.accept(serviceJsonBuilder);
JsonObjectBuilder.JsonObject[] chartData =
customCharts.stream()
.map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors))
.filter(Objects::nonNull)
.toArray(JsonObjectBuilder.JsonObject[]::new);
serviceJsonBuilder.appendField("id", serviceId);
serviceJsonBuilder.appendField("customCharts", chartData);
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
baseJsonBuilder.appendField("serverUUID", serverUuid);
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
scheduler.execute(
() -> {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logErrors) {
errorLogger.accept("Could not submit bStats metrics data", e);
}
}
});
}
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
if (logSentData) {
infoLogger.accept("Sent bStats metrics data: " + data.toString());
}
String url = String.format(REPORT_URL, platform);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
infoLogger.accept("Sent data to bStats and received response: " + builder);
}
}
/** Checks that the class was properly relocated. */
private void checkRelocation() {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this little
// "trick" ... :D
final String defaultPackage =
new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
final String examplePackage =
new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure no one just copy & pastes the example and uses the wrong package
// names
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
/**
* Gzips the given string.
*
* @param str The string to gzip.
* @return The gzipped string.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
protected CustomChart(String chartId) {
if (chartId == null) {
throw new IllegalArgumentException("chartId must not be null");
}
this.chartId = chartId;
}
public JsonObjectBuilder.JsonObject getRequestJsonObject(
BiConsumer<String, Throwable> errorLogger, boolean logErrors) {
JsonObjectBuilder builder = new JsonObjectBuilder();
builder.appendField("chartId", chartId);
try {
JsonObjectBuilder.JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
builder.appendField("data", data);
} catch (Throwable t) {
if (logErrors) {
errorLogger.accept("Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return builder.build();
}
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
/**
* An extremely simple JSON builder.
*
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
* for its use-case.
*/
public static class JsonObjectBuilder {
private StringBuilder builder = new StringBuilder();
private boolean hasAtLeastOneField = false;
public JsonObjectBuilder() {
builder.append("{");
}
/**
* Appends a null field to the JSON.
*
* @param key The key of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendNull(String key) {
appendFieldUnescaped(key, "null");
return this;
}
/**
* Appends a string field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("JSON value must not be null");
}
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
return this;
}
/**
* Appends an integer field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int value) {
appendFieldUnescaped(key, String.valueOf(value));
return this;
}
/**
* Appends an object to the JSON.
*
* @param key The key of the field.
* @param object The object.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject object) {
if (object == null) {
throw new IllegalArgumentException("JSON object must not be null");
}
appendFieldUnescaped(key, object.toString());
return this;
}
/**
* Appends a string array to the JSON.
*
* @param key The key of the field.
* @param values The string array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values)
.map(value -> "\"" + escape(value) + "\"")
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an integer array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an object array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends a field to the object.
*
* @param key The key of the field.
* @param escapedValue The escaped value of the field.
*/
private void appendFieldUnescaped(String key, String escapedValue) {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
if (key == null) {
throw new IllegalArgumentException("JSON key must not be null");
}
if (hasAtLeastOneField) {
builder.append(",");
}
builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
hasAtLeastOneField = true;
}
/**
* Builds the JSON string and invalidates this builder.
*
* @return The built JSON string.
*/
public JsonObject build() {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
JsonObject object = new JsonObject(builder.append("}").toString());
builder = null;
return object;
}
/**
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
*
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
*
* @param value The value to escape.
* @return The escaped value.
*/
private static String escape(String value) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"') {
builder.append("\\\"");
} else if (c == '\\') {
builder.append("\\\\");
} else if (c <= '\u000F') {
builder.append("\\u000").append(Integer.toHexString(c));
} else if (c <= '\u001F') {
builder.append("\\u00").append(Integer.toHexString(c));
} else {
builder.append(c);
}
}
return builder.toString();
}
/**
* A super simple representation of a JSON object.
*
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
* JsonObject)}.
*/
public static class JsonObject {
private final String value;
private JsonObject(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
}

View File

@ -731,7 +731,14 @@ public final class SubProxy extends BungeeCommon implements Listener {
if (getReconnectHandler() != null && getReconnectHandler().getClass().equals(SmartFallback.class))
setReconnectHandler(new SmartFallback(config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()))); // Re-initialize Smart Fallback
if (plugin != null) new Metrics(plugin, 1406).appendAppData();
if (plugin != null) Try.none.run(() -> new Metrics(plugin, 1406)
.addCustomChart(new Metrics.SingleLineChart("managed_hosts", () -> {
return hosts.size();
})).addCustomChart(new Metrics.SingleLineChart("subdata_connected", () -> {
final SubDataServer subdata = this.subdata;
return (subdata != null)? subdata.getClients().size() : 0;
})).addCustomChart(Util.reflect(Metrics.class.getDeclaredField("PLAYER_VERSIONS"), null))
);
new Timer("SubServers.Bungee::Routine_Update_Check").schedule(new TimerTask() {
@SuppressWarnings("unchecked")
@Override

View File

@ -22,6 +22,7 @@ public class SubAddHostEvent extends Event implements SubEvent {
* @param host Host to be added
*/
public SubAddHostEvent(UUID player, String host) {
super(true);
Util.nullpo(host);
this.player = player;
this.host = host;

View File

@ -18,6 +18,7 @@ public class SubAddProxyEvent extends Event implements SubEvent {
* @param proxy Host Being Added
*/
public SubAddProxyEvent(String proxy) {
super(true);
Util.nullpo(proxy);
this.proxy = proxy;
}

View File

@ -23,6 +23,7 @@ public class SubAddServerEvent extends Event implements SubEvent {
* @param server Server Starting
*/
public SubAddServerEvent(UUID player, String host, String server) {
super(true);
Util.nullpo(server);
this.player = player;
this.host = host;

View File

@ -36,6 +36,7 @@ public class SubCreateEvent extends Event implements SubEvent {
* @param port Server Port Number
*/
public SubCreateEvent(UUID player, String host, String name, String template, Version version, int port, boolean update) {
super(true);
Util.nullpo(host, name, template, port);
this.player = player;
this.update = update;

View File

@ -37,6 +37,7 @@ public class SubCreatedEvent extends Event implements SubEvent {
* @param port Server Port Number
*/
public SubCreatedEvent(UUID player, String host, String name, String template, Version version, int port, boolean update, boolean success) {
super(true);
Util.nullpo(host, name, template, port);
this.player = player;
this.success = success;

View File

@ -28,6 +28,7 @@ public class SubEditServerEvent extends Event implements SubEvent {
* @param edit Edit to make
*/
public SubEditServerEvent(UUID player, String server, Pair<String, ?> edit) {
super(true);
Util.nullpo(server, edit);
ObjectMap<String> section = new ObjectMap<String>();
section.set(".", edit.value());

View File

@ -17,6 +17,7 @@ public class SubNetworkConnectEvent extends Event implements SubEvent {
* SubData Network Connect Event
*/
public SubNetworkConnectEvent(DataClient network) {
super(true);
Util.nullpo(network);
this.network = network;
}

View File

@ -19,6 +19,7 @@ public class SubNetworkDisconnectEvent extends Event implements SubEvent {
* SubData Network Disconnect Event
*/
public SubNetworkDisconnectEvent(DataClient network, DisconnectReason reason) {
super(true);
Util.nullpo(network, reason);
this.network = network;
this.reason = reason;

View File

@ -22,6 +22,7 @@ public class SubRemoveHostEvent extends Event implements SubEvent {
* @param host Server Starting
*/
public SubRemoveHostEvent(UUID player, String host) {
super(true);
Util.nullpo(host);
this.player = player;
this.host = host;

View File

@ -18,6 +18,7 @@ public class SubRemoveProxyEvent extends Event implements SubEvent {
* @param proxy Host Being Added
*/
public SubRemoveProxyEvent(String proxy) {
super(true);
Util.nullpo(proxy);
this.proxy = proxy;
}

View File

@ -23,6 +23,7 @@ public class SubRemoveServerEvent extends Event implements SubEvent {
* @param server Server Starting
*/
public SubRemoveServerEvent(UUID player, String host, String server) {
super(true);
Util.nullpo(server);
this.player = player;
this.host = host;

View File

@ -26,6 +26,7 @@ public class SubSendCommandEvent extends Event implements SubEvent {
* @param target Player that will send
*/
public SubSendCommandEvent(UUID player, String server, String command, UUID target) {
super(true);
Util.nullpo(server, command);
this.player = player;
this.server = server;

View File

@ -23,6 +23,7 @@ public class SubStartEvent extends Event implements SubEvent {
* @param server Server Starting
*/
public SubStartEvent(UUID player, String server) {
super(true);
Util.nullpo(server);
this.player = player;
this.server = server;

View File

@ -19,6 +19,7 @@ public class SubStartedEvent extends Event implements SubEvent {
* @param server Server that Started
*/
public SubStartedEvent(String server) {
super(true);
Util.nullpo(server);
this.server = server;
}

View File

@ -25,6 +25,7 @@ public class SubStopEvent extends Event implements SubEvent {
* @param force If it was a Forced Shutdown
*/
public SubStopEvent(UUID player, String server, boolean force) {
super(true);
Util.nullpo(server, force);
this.player = player;
this.server = server;

View File

@ -18,6 +18,7 @@ public class SubStoppedEvent extends Event implements SubEvent {
* @param server Server that Stopped
*/
public SubStoppedEvent(String server) {
super(true);
Util.nullpo(server);
this.server = server;
}

View File

@ -102,7 +102,7 @@ public class DefaultUIRenderer extends UIRenderer {
public void hostMenu(final int page) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Host-Menu.Title")));
plugin.api.getHosts(hosts -> plugin.api.getGroups(groups -> {
plugin.api.getHosts(hosts -> plugin.api.getGroups(groups -> Bukkit.getScheduler().runTask(plugin, () -> {
setDownloading(null);
lastVisitedObjects[0] = null;
lastPage = page;
@ -229,12 +229,12 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
}));
})));
}
public void hostAdmin(final String name) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Host-Admin.Title").replace("$str$", name)));
plugin.api.getHost(name, host -> {
plugin.api.getHost(name, host -> Bukkit.getScheduler().runTask(plugin, () -> {
windowHistory.add(() -> hostAdmin(name));
if (host == null) {
if (hasHistory()) back();
@ -342,7 +342,7 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(this.player).openInventory(inv);
open = true;
}
});
}));
}
public void hostCreator(final CreatorOptions options) {
@ -350,7 +350,7 @@ public class DefaultUIRenderer extends UIRenderer {
if (!options.init()) windowHistory.add(() -> hostCreator(options));
lastVisitedObjects[0] = options;
plugin.api.getHost(options.getHost(), host -> {
plugin.api.getHost(options.getHost(), host -> Bukkit.getScheduler().runTask(plugin, () -> {
if (host == null || !host.isAvailable() || !host.isEnabled()) {
lastVisitedObjects[0] = null;
if (hasHistory()) back();
@ -469,14 +469,14 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
}
});
}));
}
public void hostCreatorTemplates(final int page, final CreatorOptions options) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Host-Creator.Edit-Template.Title").replace("$str$", options.getHost())));
options.init();
lastVisitedObjects[0] = options;
plugin.api.getHost(options.getHost(), host -> {
plugin.api.getHost(options.getHost(), host -> Bukkit.getScheduler().runTask(plugin, () -> {
if (host == null || !host.isAvailable() || !host.isEnabled()) {
lastVisitedObjects[0] = null;
if (hasHistory()) back();
@ -582,12 +582,12 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
}
});
}));
}
public void hostPlugin(final int page, final String name) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Host-Plugin.Title").replace("$str$", name)));
plugin.api.getHost(name, host -> {
plugin.api.getHost(name, host -> Bukkit.getScheduler().runTask(plugin, () -> {
windowHistory.add(() -> hostPlugin(page, name));
if (host == null) {
if (hasHistory()) back();
@ -690,12 +690,12 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
}
});
}));
}
public void groupMenu(final int page) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Group-Menu.Title")));
plugin.api.getServers(servers -> {
plugin.api.getServers(servers -> Bukkit.getScheduler().runTask(plugin, () -> {
setDownloading(null);
lastVisitedObjects[0] = null;
lastPage = page;
@ -822,14 +822,14 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
});
}));
}
public void serverMenu(final int page, final String host, final String group) {
setDownloading(ChatColor.stripColor((host == null)?((group == null)?plugin.api.getLang("SubServers", "Interface.Server-Menu.Title"):((group.length() == 0)?plugin.api.getLang("SubServers", "Interface.Group-SubServer.Title-Ungrouped"):plugin.api.getLang("SubServers", "Interface.Group-SubServer.Title").replace("$str$", group))):plugin.api.getLang("SubServers", "Interface.Host-SubServer.Title").replace("$str$", host)));
Value<String> hostname = new Container<String>(host);
Value<List<Server>> servercontainer = new Container<List<Server>>(new LinkedList<Server>());
Runnable renderer = () -> {
Runnable renderer = () -> Bukkit.getScheduler().runTask(plugin, () -> {
setDownloading(null);
lastPage = page;
@ -987,7 +987,7 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
};
});
if (host != null && host.length() > 0) {
plugin.api.getHost(host, object -> {
@ -1018,7 +1018,7 @@ public class DefaultUIRenderer extends UIRenderer {
public void serverAdmin(final String name) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.Server-Admin.Title").replace("$str$", name)));
BiConsumer<Server, Host> renderer = (server, host) -> {
BiConsumer<Server, Host> renderer = (server, host) -> Bukkit.getScheduler().runTask(plugin, () -> {
setDownloading(null);
lastVisitedObjects[0] = server;
ItemStack block;
@ -1226,7 +1226,7 @@ public class DefaultUIRenderer extends UIRenderer {
player.openInventory(inv);
open = true;
};
});
plugin.api.getServer(name, server -> {
windowHistory.add(() -> serverAdmin(name));
@ -1250,7 +1250,7 @@ public class DefaultUIRenderer extends UIRenderer {
public void serverPlugin(final int page, final String name) {
setDownloading(ChatColor.stripColor(plugin.api.getLang("SubServers", "Interface.SubServer-Plugin.Title").replace("$str$", name)));
plugin.api.getServer(name, server -> {
plugin.api.getServer(name, server -> Bukkit.getScheduler().runTask(plugin, () -> {
windowHistory.add(() -> serverPlugin(page, name));
if (server == null) {
if (hasHistory()) back();
@ -1353,6 +1353,6 @@ public class DefaultUIRenderer extends UIRenderer {
Bukkit.getPlayer(player).openInventory(inv);
open = true;
}
});
}));
}
}

View File

@ -1,5 +1,7 @@
package net.ME1312.SubServers.Client.Bukkit.Library;
import net.ME1312.SubServers.Client.Bukkit.SubAPI;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
@ -23,6 +25,9 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
* SubServers BStats Metrics Implementation
*/
public class Metrics {
private final Plugin plugin;
@ -86,13 +91,21 @@ public class Metrics {
logResponseStatusText);
}
/**
* Add subservers platform information as custom charts
*/
public Metrics addPlatformCharts() {
return addCustomChart(new SimplePie("subservers_version", () -> SubAPI.getInstance().getPluginVersion().toString()));
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
public Metrics addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
return this;
}
private void appendPlatformData(JsonObjectBuilder builder) {
@ -129,7 +142,7 @@ public class Metrics {
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "2.2.1";
public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
@ -214,6 +227,7 @@ public class Metrics {
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from bStats
startSubmitting();
}
}
@ -350,9 +364,9 @@ public class Metrics {
}
}
public static class AdvancedBarChart extends CustomChart {
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, int[]>> callable;
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
@ -360,99 +374,33 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
@ -500,6 +448,76 @@ public class Metrics {
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
@ -534,32 +552,6 @@ public class Metrics {
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
@ -586,9 +578,9 @@ public class Metrics {
}
}
public static class DrilldownPie extends CustomChart {
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
@ -596,33 +588,29 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (reallyAllSkipped) {
if (allSkipped) {
// Null = skip the chart
return null;
}
@ -630,6 +618,32 @@ public class Metrics {
}
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*

View File

@ -591,9 +591,9 @@ public final class Placeholders {
}
public final class Cache {
private Map<String, Proxy> proxies = Collections.emptyMap();
private Map<String, Host> hosts = Collections.emptyMap();
private Map<String, Server> servers = Collections.emptyMap();
private Map<String, Proxy> proxies = new TreeMap<>();
private Map<String, Host> hosts = new TreeMap<>();
private Map<String, Server> servers = new TreeMap<>();
private Proxy master = null;
private Listener events = new Events();

View File

@ -0,0 +1,42 @@
package net.ME1312.SubServers.Client.Bukkit.Library;
import net.ME1312.SubServers.Client.Common.Network.API.Server;
import net.ME1312.SubServers.Client.Common.Network.API.SubServer;
public enum SignState {
UNKNOWN(0, "Signs.Text.Unknown"),
OFFLINE(1, "Signs.Text.Offline"),
STARTING(3, "Signs.Text.Starting"),
ONLINE(4, "Signs.Text.Online"),
STOPPING(2, "Signs.Text.Stopping"),
;
public final byte priority;
public final String text;
SignState(int priority, String text) {
this.priority = (byte) priority;
this.text = text;
}
public static SignState determine(SubServer server) {
if (!server.isRunning()) {
return SignState.OFFLINE;
} else if (server.isStopping()) {
return SignState.STOPPING;
} else if (server.isOnline()) {
return SignState.ONLINE;
} else {
return SignState.STARTING;
}
}
public static SignState determine(Server server) {
if (server instanceof SubServer) {
return determine((SubServer) server);
} else if (server.getSubData()[0] == null) {
return SignState.UNKNOWN;
} else {
return SignState.ONLINE;
}
}
}

View File

@ -115,9 +115,7 @@ public class SubProtocol extends SubDataProtocol {
return instance;
}
@SuppressWarnings("deprecation")
private Logger getLogger(int channel) {
SubPlugin plugin = SubAPI.getInstance().getInternals();
Logger log = Logger.getAnonymousLogger();
log.setUseParentHandlers(false);
log.addHandler(new Handler() {
@ -127,11 +125,7 @@ public class SubProtocol extends SubDataProtocol {
@Override
public void publish(LogRecord record) {
if (open) {
if (plugin.isEnabled()) {
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getLogger().log(record.getLevel(), prefix + " > " + record.getMessage(), record.getParameters()));
} else {
Bukkit.getLogger().log(record.getLevel(), prefix + " > " + record.getMessage(), record.getParameters());
}
Bukkit.getLogger().log(record.getLevel(), prefix + " > " + record.getMessage(), record.getParameters());
}
}
@ -193,7 +187,7 @@ public class SubProtocol extends SubDataProtocol {
public SubDataClient open(Logger logger, InetAddress address, int port) throws IOException {
SubPlugin plugin = SubAPI.getInstance().getInternals();
return open(event -> {
if (plugin.isEnabled()) Bukkit.getScheduler().runTask(plugin, event);
if (plugin.isEnabled()) Bukkit.getScheduler().runTaskAsynchronously(plugin, event);
else event.run();
}, logger, address, port);
}

View File

@ -63,7 +63,7 @@ public final class SubCommand extends Command {
if (sender.hasPermission("subservers.command")) {
if (args.length > 0) {
if (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("?")) {
sender.sendMessage(printHelp(sender, label));
sender.sendMessage(printHelp(label));
} else if (args[0].equalsIgnoreCase("version") || args[0].equalsIgnoreCase("ver")) {
sender.sendMessage(plugin.api.getLang("SubServers", "Command.Version").replace("$str$", "SubServers.Client.Bukkit"));
sender.sendMessage(ChatColor.WHITE + " " + Platform.getSystemName() + ' ' + Platform.getSystemVersion() + ((Platform.getSystemBuild() != null)?" (" + Platform.getSystemBuild() + ')':"") + ((!Platform.getSystemArchitecture().equals("unknown"))?" [" + Platform.getSystemArchitecture() + ']':"") + ChatColor.RESET + ',');
@ -838,7 +838,7 @@ public final class SubCommand extends Command {
if (plugin.gui != null && sender instanceof Player && sender.hasPermission("subservers.interface")) {
plugin.gui.getRenderer((Player) sender).newUI();
} else {
sender.sendMessage(printHelp(sender, label));
sender.sendMessage(printHelp(label));
}
}
} else if (args.length > 0 && (args[0].equalsIgnoreCase("tp") || args[0].equalsIgnoreCase("teleport"))) {
@ -1118,7 +1118,7 @@ public final class SubCommand extends Command {
}
}
private String[] printHelp(CommandSender sender, String label) {
private String[] printHelp(String label) {
return new String[]{
plugin.api.getLang("SubServers", "Command.Help.Header"),
plugin.api.getLang("SubServers", "Command.Help.Help").replace("$str$", label.toLowerCase() + " help"),

View File

@ -52,6 +52,7 @@ public final class SubPlugin extends JavaPlugin {
public SubProtocol subprotocol;
public UIHandler gui = null;
public SubSigns signs = null;
public final Version version;
public final SubAPI api = new SubAPI(this);
public final Placeholders phi = new Placeholders(this);
@ -130,7 +131,7 @@ public final class SubPlugin extends JavaPlugin {
gui = new DefaultUIHandler(this);
if (api.access.value > NO_COMMANDS.value && !config.get().getMap("Settings").getBoolean("API-Only-Mode", false)) {
Bukkit.getPluginManager().registerEvents(new SubSigns(this, new File(dir, "signs.dat")), this);
signs = new SubSigns(this, new File(dir, "signs.dat"));
CommandMap cmd = Util.reflect(Bukkit.getServer().getClass().getDeclaredField("commandMap"), Bukkit.getServer());
cmd.register("subservers", new SubCommand(this, "subservers"));
@ -176,9 +177,9 @@ public final class SubPlugin extends JavaPlugin {
if (notifyPlugins) {
List<Runnable> listeners = api.reloadListeners;
if (listeners.size() > 0) {
for (Object obj : listeners) {
for (Runnable listener : listeners) {
try {
((Runnable) obj).run();
listener.run();
} catch (Throwable e) {
new InvocationTargetException(e, "Problem reloading plugin").printStackTrace();
}
@ -206,10 +207,10 @@ public final class SubPlugin extends JavaPlugin {
} catch (IOException e) {
Bukkit.getLogger().info("SubData > Connection was unsuccessful, retrying in " + reconnect + " seconds");
Bukkit.getScheduler().runTaskLater(SubPlugin.this, this, reconnect * 20);
Bukkit.getScheduler().runTaskLater(SubPlugin.this, this, reconnect * 20L);
}
}
}, (disconnect == null)?0:reconnect * 20);
}, (disconnect == null)?0:reconnect * 20L);
}
}
@ -230,7 +231,7 @@ public final class SubPlugin extends JavaPlugin {
}
subdata.clear();
subdata.put(0, null);
SubSigns.save();
if (signs != null) signs.save();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}

View File

@ -7,6 +7,7 @@ import net.ME1312.SubServers.Client.Bukkit.Event.SubStartedEvent;
import net.ME1312.SubServers.Client.Bukkit.Event.SubStopEvent;
import net.ME1312.SubServers.Client.Bukkit.Event.SubStoppedEvent;
import net.ME1312.SubServers.Client.Bukkit.Library.Compatibility.OfflineBlock;
import net.ME1312.SubServers.Client.Bukkit.Library.SignState;
import net.ME1312.SubServers.Client.Common.Network.API.Host;
import net.ME1312.SubServers.Client.Common.Network.API.Server;
import net.ME1312.SubServers.Client.Common.Network.API.SubServer;
@ -37,20 +38,21 @@ import java.util.function.Supplier;
* SubServers Signs Class
*/
public class SubSigns implements Listener {
private static final HashMap<OfflineBlock, String> data = new HashMap<OfflineBlock, String>();
private static final HashMap<String, Location> locations = new HashMap<String, Location>();
private static HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>();
private static File file;
private final HashMap<OfflineBlock, String> data = new HashMap<OfflineBlock, String>();
private final HashMap<String, Location> locations = new HashMap<String, Location>();
private HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>();
private final File file;
private final SubPlugin plugin;
private boolean active = false;
SubSigns(SubPlugin plugin, File file) throws IOException {
this.plugin = plugin;
SubSigns.file = file;
load();
this.file = file;
this.load();
Bukkit.getPluginManager().registerEvents(this, plugin);
}
public static void save() throws IOException {
public void save() throws IOException {
if (!data.isEmpty() || (file.exists() && !file.delete())) {
FileOutputStream raw = new FileOutputStream(file, false);
EscapedOutputStream escaped = new EscapedOutputStream(raw, '\u001B', '\u0003');
@ -130,11 +132,11 @@ public class SubSigns implements Listener {
Supplier<?> translator = translate(name);
Location location = e.getBlock().getLocation();
HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>(SubSigns.signs);
HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>(this.signs);
signs.put(location, translator);
SubSigns.data.put(new OfflineBlock(location), name);
SubSigns.signs = signs;
SubSigns.locations.put(name.toLowerCase(), location);
this.data.put(new OfflineBlock(location), name);
this.signs = signs;
this.locations.put(name.toLowerCase(), location);
listen();
refresh(e.getBlock(), translator);
@ -178,6 +180,30 @@ public class SubSigns implements Listener {
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void refresh(SubStartEvent e) {
refresh(e.getServer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void refresh(SubStartedEvent e) {
refresh(e.getServer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void refresh(SubStopEvent e) {
refresh(e.getServer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void refresh(SubStoppedEvent e) {
refresh(e.getServer());
}
private void refresh(String name) {
refresh(plugin.phi.cache.getSubServer(name));
}
private void refresh(SubServer server) {
if (server != null && plugin.lang != null) {
Location location = locations.get(server.getName().toLowerCase());
@ -195,95 +221,60 @@ public class SubSigns implements Listener {
}
}
private enum Text {
UNKNOWN(0, "Signs.Text.Unknown"),
OFFLINE(1, "Signs.Text.Offline"),
STARTING(3, "Signs.Text.Starting"),
ONLINE(4, "Signs.Text.Online"),
STOPPING(2, "Signs.Text.Stopping"),
;
private final byte priority;
private final String text;
Text(int priority, String text) {
this.priority = (byte) priority;
this.text = text;
}
private static Text determine(SubServer server) {
if (!server.isRunning()) {
return Text.OFFLINE;
} else if (server.isStopping()) {
return Text.STOPPING;
} else if (server.isOnline()) {
return Text.ONLINE;
} else {
return Text.STARTING;
}
}
private static Text determine(Server server) {
if (server instanceof SubServer) {
return determine((SubServer) server);
} else if (server.getSubData()[0] == null) {
return Text.UNKNOWN;
} else {
return Text.ONLINE;
}
}
}
@SuppressWarnings("unchecked")
private void refresh(Block block, Supplier<?> translator) {
if (block.getState() instanceof Sign) {
Object object = translator.get();
String name;
int players = 0;
Bukkit.getScheduler().runTask(plugin, () -> {
if (block.getState() instanceof Sign) {
Object object = translator.get();
String name;
int players = 0;
Sign sign = (Sign) block.getState();
Text state = Text.UNKNOWN;
Sign sign = (Sign) block.getState();
SignState state = SignState.UNKNOWN;
if (object instanceof Server) {
Server server = (Server) object;
state = Text.determine(server);
name = server.getDisplayName();
players = server.getRemotePlayers().size();
if (object instanceof Server) {
Server server = (Server) object;
state = SignState.determine(server);
name = server.getDisplayName();
players = server.getRemotePlayers().size();
} else if (object instanceof Pair) {
Pair<String, List<Server>> group = (Pair<String, List<Server>>) object;
name = group.key();
} else if (object instanceof Pair) {
Pair<String, List<Server>> group = (Pair<String, List<Server>>) object;
name = group.key();
Text incoming;
for (Server server : group.value()) {
players += server.getRemotePlayers().size();
incoming = Text.determine(server);
if (incoming.priority > state.priority)
state = incoming;
SignState incoming;
for (Server server : group.value()) {
players += server.getRemotePlayers().size();
incoming = SignState.determine(server);
if (incoming.priority > state.priority)
state = incoming;
}
} else if (object instanceof Host) {
Host host = (Host) object;
name = host.getDisplayName();
SignState incoming;
for (SubServer server : host.getSubServers().values()) {
players += server.getRemotePlayers().size();
incoming = SignState.determine(server);
if (incoming.priority > state.priority)
state = incoming;
}
} else if (object instanceof String) {
name = (String) object;
} else {
return;
}
} else if (object instanceof Host) {
Host host = (Host) object;
name = host.getDisplayName();
Text incoming;
for (SubServer server : host.getSubServers().values()) {
players += server.getRemotePlayers().size();
incoming = Text.determine(server);
if (incoming.priority > state.priority)
state = incoming;
String[] text = plugin.phi.replace(null, plugin.api.getLang("SubServers", state.text).replace("$str$", name).replace("$int$", NumberFormat.getInstance().format(players))).split("\n", 4);
for (int i = 0; i < 4; ++i) if (i < text.length) {
sign.setLine(i, text[i]);
} else {
sign.setLine(i, "");
}
} else if (object instanceof String) {
name = (String) object;
} else {
return;
sign.update();
}
String[] text = plugin.phi.replace(null, plugin.api.getLang("SubServers", state.text).replace("$str$", name).replace("$int$", NumberFormat.getInstance().format(players))).split("\n", 4);
for (int i = 0; i < 4; ++i) if (i < text.length) {
sign.setLine(i, text[i]);
} else {
sign.setLine(i, "");
}
Bukkit.getScheduler().runTask(plugin, sign::update);
}
});
}
@SuppressWarnings("unchecked")
@ -307,12 +298,12 @@ public class SubSigns implements Listener {
return;
}
Text incoming, state = Text.UNKNOWN;
SignState incoming, state = SignState.UNKNOWN;
List<Server> selected = new ArrayList<>();
for (Server server : servers) {
incoming = Text.determine(server);
if (incoming != Text.STOPPING) {
if (incoming == Text.OFFLINE) {
incoming = SignState.determine(server);
if (incoming != SignState.STOPPING) {
if (incoming == SignState.OFFLINE) {
SubServer subserver = (SubServer) server;
if (!subserver.isEnabled() || !subserver.isAvailable() || subserver.getCurrentIncompatibilities().size() != 0) continue;
}
@ -329,7 +320,7 @@ public class SubSigns implements Listener {
if (selected.size() > 0) {
Server server = selected.get(new Random().nextInt(selected.size()));
if (state == Text.OFFLINE) {
if (state == SignState.OFFLINE) {
((SubServer) server).start();
} else {
player.sendMessage(plugin.api.getLang("SubServers", "Command.Teleport").replace("$name$", player.getName()).replace("$str$", server.getDisplayName()));
@ -350,9 +341,9 @@ public class SubSigns implements Listener {
String name = data.remove(new OfflineBlock(location));
if (name != null) locations.remove(name.toLowerCase());
HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>(SubSigns.signs);
HashMap<Location, Supplier<?>> signs = new HashMap<Location, Supplier<?>>(this.signs);
signs.remove(location);
SubSigns.signs = signs;
this.signs = signs;
player.sendMessage(plugin.api.getLang("SubServers", "Signs.Delete"));
} else {
@ -360,24 +351,4 @@ public class SubSigns implements Listener {
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void start(SubStartEvent e) {
refresh(plugin.phi.cache.getSubServer(e.getServer()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void started(SubStartedEvent e) {
refresh(plugin.phi.cache.getSubServer(e.getServer()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void stopping(SubStopEvent e) {
refresh(plugin.phi.cache.getSubServer(e.getServer()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void stopped(SubStoppedEvent e) {
refresh(plugin.phi.cache.getSubServer(e.getServer()));
}
}

View File

@ -24,7 +24,7 @@
<dependency>
<groupId>net.ME1312.SubData</groupId>
<artifactId>Client</artifactId>
<version>22w11a</version>
<version>22w11c</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -22,9 +22,7 @@ import java.util.*;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
*
* Check out https://bStats.org/ to learn more about bStats!
* SubServers BStats Metrics Implementation
*/
public class Metrics {

View File

@ -178,9 +178,9 @@ public final class SubPlugin {
if (notifyPlugins) {
List<Runnable> listeners = api.reloadListeners;
if (listeners.size() > 0) {
for (Object obj : listeners) {
for (Runnable listener : listeners) {
try {
((Runnable) obj).run();
listener.run();
} catch (Throwable e) {
new InvocationTargetException(e, "Problem reloading plugin").printStackTrace();
}

View File

@ -33,6 +33,12 @@
<version>1.8-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.ME1312.SubServers</groupId>
<artifactId>SubServers.Bungee.Common</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.ME1312.SubServers</groupId>
<artifactId>SubServers.Bungee</artifactId>

View File

@ -37,7 +37,7 @@ public final class ConsolePlugin extends Plugin implements Listener {
getProxy().getPluginManager().registerCommand(this, new ConsoleCommand.AUTO_POPOUT(this, "apopout"));
getProxy().getPluginManager().registerCommand(this, new ConsoleCommand.AUTO_POPOUT(this, "autopopout"));
new Metrics(this, 3853).appendPluginData();
new Metrics(this, 3853).addPlatformCharts();
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

View File

@ -34,7 +34,7 @@ public class JNA {
File jna = new File(library, "jna-" + JNA_VERSION + ".jar");
jna.getParentFile().mkdirs();
if (!jna.exists()) {
log.info.println("Downloading JNA Library v" + JNA_VERSION);
log.info.println("Downloading JNA v" + JNA_VERSION);
announced = true;
try (InputStream in = new URL(JNA_DOWNLOAD.replace("$1", "jna")).openStream()) {
Files.copy(in, jna.toPath(), StandardCopyOption.REPLACE_EXISTING);
@ -46,7 +46,7 @@ public class JNA {
File platform = new File(library, "jna-platform-" + JNA_VERSION + ".jar");
platform.getParentFile().mkdirs();
if (!platform.exists()) {
if (!announced) log.info.println("Downloading JNA Library v" + JNA_VERSION);
if (!announced) log.info.println("Downloading JNA platform v" + JNA_VERSION);
announced = true;
try (InputStream in = new URL(JNA_DOWNLOAD.replace("$1", "jna-platform")).openStream()) {
Files.copy(in, platform.toPath(), StandardCopyOption.REPLACE_EXISTING);
@ -56,15 +56,15 @@ public class JNA {
}
}
if (jna.exists() && platform.exists()) {
if (announced) log.info.println("Loading JNA Library");
if (announced) log.info.println("JNA download complete");
try {
JNA = new URLClassLoader(new URL[]{jna.toURI().toURL(), platform.toURI().toURL()});
} catch (Throwable e) {
log.error.println("Could not load JNA Library:");
log.error.println("Couldn't load JNA:");
log.error.println(e);
}
} else {
log.error.println("Could not load JNA Library:");
log.error.println("Couldn't load JNA:");
log.error.println(new FileNotFoundException());
}
}

View File

@ -20,9 +20,7 @@ import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
*
* Check out https://bStats.org/ to learn more about bStats!
* SubServers BStats Metrics Implementation
*/
public class Metrics {

View File

@ -28,7 +28,7 @@ public class PacketInExReload implements PacketObjectIn<Integer> {
@Override
public void receive(SubDataSender client, ObjectMap<Integer> data) {
Logger log = Try.all.get(() -> Util.reflect(SubDataClient.class.getDeclaredField("log"), client.getConnection()), null);
if (data != null && data.contains(0x0000)) log.warning("Received request for a plugin reload: " + data.getString(0x0000));
if (data != null && data.contains(0x0000)) log.warning("Received request for an app reload: " + data.getString(0x0000));
// else log.warning("Received request for a plugin reload");
new Thread(() -> {
try {

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -2,8 +2,8 @@ package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.Galaxi.Library.Version.Version;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.ME1312.SubServers.Client.Common.Network.API.SubServer;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Sync.SubAPI;
import net.md_5.bungee.api.plugin.Event;

View File

@ -2,8 +2,8 @@ package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.Galaxi.Library.Version.Version;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.ME1312.SubServers.Client.Common.Network.API.SubServer;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Sync.SubAPI;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,7 +5,7 @@ import net.ME1312.Galaxi.Library.Container.Pair;
import net.ME1312.Galaxi.Library.Map.ObjectMap;
import net.ME1312.Galaxi.Library.Map.ObjectMapValue;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -2,7 +2,7 @@ package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubData.Client.DataClient;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -3,7 +3,7 @@ package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubData.Client.DataClient;
import net.ME1312.SubData.Client.Library.DisconnectReason;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -1,7 +1,7 @@
package net.ME1312.SubServers.Sync.Event;
import net.ME1312.Galaxi.Library.Util;
import net.ME1312.SubServers.Sync.Library.SubEvent;
import net.ME1312.SubServers.Bungee.Library.SubEvent;
import net.md_5.bungee.api.plugin.Event;

View File

@ -17,13 +17,13 @@ import net.ME1312.SubServers.Bungee.BungeeCommon;
import net.ME1312.SubServers.Bungee.Library.Compatibility.Logger;
import net.ME1312.SubServers.Bungee.Library.Fallback.FallbackState;
import net.ME1312.SubServers.Bungee.Library.Fallback.SmartFallback;
import net.ME1312.SubServers.Bungee.Library.Metrics;
import net.ME1312.SubServers.Client.Common.Network.Packet.PacketDisconnectPlayer;
import net.ME1312.SubServers.Sync.Event.SubAddServerEvent;
import net.ME1312.SubServers.Sync.Event.SubRemoveServerEvent;
import net.ME1312.SubServers.Sync.Event.SubStartEvent;
import net.ME1312.SubServers.Sync.Event.SubStoppedEvent;
import net.ME1312.SubServers.Sync.Library.ConfigUpdater;
import net.ME1312.SubServers.Sync.Library.Metrics;
import net.ME1312.SubServers.Sync.Network.Packet.PacketExSyncPlayer;
import net.ME1312.SubServers.Sync.Network.SubProtocol;
import net.ME1312.SubServers.Sync.Server.CachedPlayer;
@ -267,7 +267,8 @@ public final class ExProxy extends BungeeCommon implements Listener {
if (getReconnectHandler() != null && getReconnectHandler().getClass().equals(SmartFallback.class))
setReconnectHandler(new SmartFallback(config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()))); // Re-initialize Smart Fallback
if (plugin != null) new Metrics(plugin, 1461).appendAppData();
if (plugin != null) Try.none.run(() -> new Metrics(plugin, 1461).addCustomChart(Util.reflect(Metrics.class.getDeclaredField("PLAYER_VERSIONS"), null)));
new Timer("SubServers.Sync::Routine_Update_Check").schedule(new TimerTask() {
@SuppressWarnings("unchecked")
@Override

View File

@ -1,7 +0,0 @@
package net.ME1312.SubServers.Sync.Library;
/**
* SubEvent Layout Class
*/
public interface SubEvent {
}

View File

@ -22,7 +22,7 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.16.0</version>
<version>2.17.2</version>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -230,7 +230,7 @@ public class ExProxy {
if (config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("Enabled", true))
proxy.getEventManager().register(this, new SmartFallback(config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>())));
metrics.make(this, 11953).appendAppData();
Try.none.run(() -> metrics.make(this, 11953).addCustomChart(Util.reflect(Metrics.class.getDeclaredField("PLAYER_VERSIONS"), null)));
new Timer("SubServers.Sync::Routine_Update_Check").schedule(new TimerTask() {
@SuppressWarnings("unchecked")
@Override

View File

@ -29,6 +29,9 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
* SubServers BStats Metrics Implementation
*/
public class Metrics {
/** A factory to create new Metrics classes. */
@ -116,17 +119,6 @@ public class Metrics {
}
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
if (metricsBase != null) {
metricsBase.addCustomChart(chart);
}
}
private static final AdvancedPie PLAYER_VERSIONS;
static {
final ProtocolVersion[] PROTOCOLS = ProtocolVersion.SUPPORTED_VERSIONS.toArray(new ProtocolVersion[0]);
@ -149,15 +141,23 @@ public class Metrics {
});
}
public void appendAppData() {
addCustomChart(PLAYER_VERSIONS);
/**
* Add subservers platform information as custom charts
*/
public Metrics addPlatformCharts() {
return addCustomChart(new SimplePie("subservers_version", () -> SubAPI.getInstance().getPluginVersion().toString())).addCustomChart(PLAYER_VERSIONS);
}
public void appendPluginData() {
addCustomChart(new SimplePie("subservers_version", () -> {
return SubAPI.getInstance().getPluginVersion().toString();
}));
addCustomChart(PLAYER_VERSIONS);
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public Metrics addCustomChart(CustomChart chart) {
if (metricsBase != null) {
metricsBase.addCustomChart(chart);
}
return this;
}
private void appendPlatformData(JsonObjectBuilder builder) {
@ -182,7 +182,7 @@ public class Metrics {
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "2.2.1";
public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
@ -267,6 +267,7 @@ public class Metrics {
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from bStats
startSubmitting();
}
}
@ -403,9 +404,9 @@ public class Metrics {
}
}
public static class AdvancedBarChart extends CustomChart {
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, int[]>> callable;
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
@ -413,99 +414,33 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
@ -553,6 +488,76 @@ public class Metrics {
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
@ -587,32 +592,6 @@ public class Metrics {
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
@ -639,9 +618,9 @@ public class Metrics {
}
}
public static class DrilldownPie extends CustomChart {
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
@ -649,33 +628,29 @@ public class Metrics {
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (reallyAllSkipped) {
if (allSkipped) {
// Null = skip the chart
return null;
}
@ -683,6 +658,32 @@ public class Metrics {
}
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*