From a0cd907242ac02dab1a98a6c0a6e01d357ff43d3 Mon Sep 17 00:00:00 2001 From: Sekwah Date: Sun, 28 Jan 2024 03:36:09 +0000 Subject: [PATCH] fix: something in the old metrics class was triggering certain antivirus --- .../bukkit/AdvancedPortalsPlugin.java | 2 +- .../bukkit/metrics/Metrics.java | 123 +++++++++--------- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/sekwah/advancedportals/bukkit/AdvancedPortalsPlugin.java b/src/main/java/com/sekwah/advancedportals/bukkit/AdvancedPortalsPlugin.java index b638df9..30051c2 100644 --- a/src/main/java/com/sekwah/advancedportals/bukkit/AdvancedPortalsPlugin.java +++ b/src/main/java/com/sekwah/advancedportals/bukkit/AdvancedPortalsPlugin.java @@ -40,7 +40,7 @@ public class AdvancedPortalsPlugin extends JavaPlugin { saveDefaultConfig(); /*Metrics metrics = */ - new Metrics(this); + new Metrics(this, 4814); ConfigAccessor config = new ConfigAccessor(this, "config.yml"); diff --git a/src/main/java/com/sekwah/advancedportals/bukkit/metrics/Metrics.java b/src/main/java/com/sekwah/advancedportals/bukkit/metrics/Metrics.java index 64dc2a9..d4c09a8 100644 --- a/src/main/java/com/sekwah/advancedportals/bukkit/metrics/Metrics.java +++ b/src/main/java/com/sekwah/advancedportals/bukkit/metrics/Metrics.java @@ -3,6 +3,7 @@ package com.sekwah.advancedportals.bukkit.metrics; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; @@ -18,6 +19,10 @@ 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.ThreadFactory; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.zip.GZIPOutputStream; @@ -29,19 +34,12 @@ import java.util.zip.GZIPOutputStream; @SuppressWarnings({"WeakerAccess", "unused"}) public class Metrics { - static { - // 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', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } + // This ThreadFactory enforces the naming convention for our Threads + private final ThreadFactory threadFactory = task -> new Thread(task, "bStats-Metrics"); + + // Executor service for requests + // We use an executor service because the Bukkit scheduler is affected by server lags + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, threadFactory); // The version of this bStats class public static final int B_STATS_VERSION = 1; @@ -50,7 +48,7 @@ public class Metrics { private static final String URL = "https://bStats.org/submitData/bukkit"; // Is bStats enabled on this server? - private final boolean enabled; + private boolean enabled; // Should failed requests be logged? private static boolean logFailedRequests; @@ -67,6 +65,9 @@ public class Metrics { // The plugin private final Plugin plugin; + // The plugin id + private final int pluginId; + // A list with all custom charts private final List charts = new ArrayList<>(); @@ -74,12 +75,15 @@ public class Metrics { * Class constructor. * * @param plugin The plugin which stats should be submitted. + * @param pluginId The id of the plugin. + * It can be found at What is my plugin id? */ - public Metrics(Plugin plugin) { + public Metrics(Plugin plugin, int pluginId) { if (plugin == null) { throw new IllegalArgumentException("Plugin cannot be null!"); } this.plugin = plugin; + this.pluginId = pluginId; // Get the config file File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); @@ -163,22 +167,24 @@ public class Metrics { * Starts the Scheduler which submits our data every 30 minutes. */ private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); + final Runnable submitTask = () -> { + if (!plugin.isEnabled()) { // Plugin was disabled + scheduler.shutdown(); + return; } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, 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); } /** @@ -194,6 +200,7 @@ public class Metrics { String pluginVersion = plugin.getDescription().getVersion(); data.addProperty("pluginName", pluginName); // Append the name of the plugin + data.addProperty("id", pluginId); // Append the id of the plugin data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin JsonArray customCharts = new JsonArray(); for (CustomChart customChart : charts) { @@ -288,7 +295,6 @@ public class Metrics { if (logFailedRequests) { this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); } - continue; // continue looping since we cannot do any other thing. } } } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } @@ -299,17 +305,14 @@ public class Metrics { data.add("plugins", pluginData); // Create a new thread for the connection to the bStats server - new Thread(new Runnable() { - @Override - public void run() { - try { - // Send the data - sendData(plugin, data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } + new Thread(() -> { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); } } }).start(); @@ -330,7 +333,7 @@ public class Metrics { throw new IllegalAccessException("This method must not be called from the main thread!"); } if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data.toString()); + plugin.getLogger().info("Sending data to bStats: " + data); } HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); @@ -348,22 +351,20 @@ public class Metrics { // Send data connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - InputStream inputStream = connection.getInputStream(); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } StringBuilder builder = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } } - bufferedReader.close(); + if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + plugin.getLogger().info("Sent data to bStats and received response: " + builder); } } @@ -379,9 +380,9 @@ public class Metrics { return null; } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - gzip.close(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } return outputStream.toByteArray(); } @@ -658,7 +659,7 @@ public class Metrics { } for (Map.Entry entry : map.entrySet()) { JsonArray categoryValues = new JsonArray(); - categoryValues.add(entry.getValue()); + categoryValues.add(new JsonPrimitive(entry.getValue())); values.add(entry.getKey(), categoryValues); } data.add("values", values); @@ -702,7 +703,7 @@ public class Metrics { allSkipped = false; JsonArray categoryValues = new JsonArray(); for (int categoryValue : entry.getValue()) { - categoryValues.add(categoryValue); + categoryValues.add(new JsonPrimitive(categoryValue)); } values.add(entry.getKey(), categoryValues); } @@ -715,4 +716,4 @@ public class Metrics { } } -} +} \ No newline at end of file