diff --git a/Spigot-Server-Patches/0034-RIP-metrics.patch b/Spigot-Server-Patches/0034-RIP-metrics.patch new file mode 100644 index 0000000000..0d99173b5e --- /dev/null +++ b/Spigot-Server-Patches/0034-RIP-metrics.patch @@ -0,0 +1,692 @@ +From b3dda52207845877ca74ff77188345c8e9f150af Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 19 Aug 2014 14:25:40 -0500 +Subject: [PATCH] RIP metrics + + +diff --git a/src/main/java/org/spigotmc/Metrics.java b/src/main/java/org/spigotmc/Metrics.java +deleted file mode 100644 +index d9c3b63..0000000 +--- a/src/main/java/org/spigotmc/Metrics.java ++++ /dev/null +@@ -1,645 +0,0 @@ +-/* +- * Copyright 2011-2013 Tyler Blair. All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without modification, are +- * permitted provided that the following conditions are met: +- * +- * 1. Redistributions of source code must retain the above copyright notice, this list of +- * conditions and the following disclaimer. +- * +- * 2. Redistributions in binary form must reproduce the above copyright notice, this list +- * of conditions and the following disclaimer in the documentation and/or other materials +- * provided with the distribution. +- * +- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED +- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +- * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +- * +- * The views and conclusions contained in the software and documentation are those of the +- * authors and contributors and should not be interpreted as representing official policies, +- * either expressed or implied, of anybody else. +- */ +-package org.spigotmc; +- +-import org.bukkit.Bukkit; +-import org.bukkit.configuration.file.YamlConfiguration; +-import org.bukkit.configuration.InvalidConfigurationException; +-import org.bukkit.plugin.Plugin; +-import org.bukkit.plugin.PluginDescriptionFile; +-import org.bukkit.scheduler.BukkitTask; +- +-import java.io.BufferedReader; +-import java.io.File; +-import java.io.IOException; +-import java.io.InputStreamReader; +-import java.io.OutputStreamWriter; +-import java.io.UnsupportedEncodingException; +-import java.net.Proxy; +-import java.net.URL; +-import java.net.URLConnection; +-import java.net.URLEncoder; +-import java.util.Collections; +-import java.util.HashSet; +-import java.util.Iterator; +-import java.util.LinkedHashSet; +-import java.util.Set; +-import java.util.Timer; +-import java.util.TimerTask; +-import java.util.UUID; +-import java.util.concurrent.TimeUnit; +-import java.util.logging.Level; +-import net.minecraft.server.MinecraftServer; +- +-/** +- *

The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.

+- * Public methods provided by this class:

+- * +- * Graph createGraph(String name);
+- * void addCustomData(BukkitMetrics.Plotter plotter);
+- * void start();
+- *
+- */ +-public class Metrics { +- +- /** +- * The current revision number +- */ +- private final static int REVISION = 6; +- /** +- * The base url of the metrics domain +- */ +- private static final String BASE_URL = "http://mcstats.org"; +- /** +- * The url used to report a server's status +- */ +- private static final String REPORT_URL = "/report/%s"; +- /** +- * The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and +- * want to change it. +- */ +- private static final String CUSTOM_DATA_SEPARATOR = "~~"; +- /** +- * Interval of time to ping (in minutes) +- */ +- private static final int PING_INTERVAL = 10; +- /** +- * All of the custom graphs to submit to metrics +- */ +- private final Set graphs = Collections.synchronizedSet(new HashSet()); +- /** +- * The default graph, used for addCustomData when you don't want a specific graph +- */ +- private final Graph defaultGraph = new Graph("Default"); +- /** +- * The plugin configuration file +- */ +- private final YamlConfiguration configuration; +- /** +- * The plugin configuration file +- */ +- private final File configurationFile; +- /** +- * Unique server id +- */ +- private final String guid; +- /** +- * Debug mode +- */ +- private final boolean debug; +- /** +- * Lock for synchronization +- */ +- private final Object optOutLock = new Object(); +- /** +- * The scheduled task +- */ +- private volatile Timer task = null; +- +- public Metrics() throws IOException { +- // load the config +- configurationFile = getConfigFile(); +- configuration = YamlConfiguration.loadConfiguration(configurationFile); +- +- // add some defaults +- configuration.addDefault("opt-out", false); +- configuration.addDefault("guid", UUID.randomUUID().toString()); +- configuration.addDefault("debug", false); +- +- // Do we need to create the file? +- if (configuration.get("guid", null) == null) { +- configuration.options().header("http://mcstats.org").copyDefaults(true); +- configuration.save(configurationFile); +- } +- +- // Load the guid then +- guid = configuration.getString("guid"); +- debug = configuration.getBoolean("debug", false); +- } +- +- /** +- * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics +- * website. Plotters can be added to the graph object returned. +- * +- * @param name The name of the graph +- * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given +- */ +- public Graph createGraph(final String name) { +- if (name == null) { +- throw new IllegalArgumentException("Graph name cannot be null"); +- } +- +- // Construct the graph object +- final Graph graph = new Graph(name); +- +- // Now we can add our graph +- graphs.add(graph); +- +- // and return back +- return graph; +- } +- +- /** +- * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend +- * +- * @param graph The name of the graph +- */ +- public void addGraph(final Graph graph) { +- if (graph == null) { +- throw new IllegalArgumentException("Graph cannot be null"); +- } +- +- graphs.add(graph); +- } +- +- /** +- * Adds a custom data plotter to the default graph +- * +- * @param plotter The plotter to use to plot custom data +- */ +- public void addCustomData(final Plotter plotter) { +- if (plotter == null) { +- throw new IllegalArgumentException("Plotter cannot be null"); +- } +- +- // Add the plotter to the graph o/ +- defaultGraph.addPlotter(plotter); +- +- // Ensure the default graph is included in the submitted graphs +- graphs.add(defaultGraph); +- } +- +- /** +- * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the +- * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200 +- * ticks. +- * +- * @return True if statistics measuring is running, otherwise false. +- */ +- public boolean start() { +- synchronized (optOutLock) { +- // Did we opt out? +- if (isOptOut()) { +- return false; +- } +- +- // Is metrics already running? +- if (task != null) { +- return true; +- } +- +- // Begin hitting the server with glorious data +- task = new Timer("Spigot Metrics Thread", true); +- +- task.scheduleAtFixedRate(new TimerTask() { +- private boolean firstPost = true; +- +- public void run() { +- try { +- // This has to be synchronized or it can collide with the disable method. +- synchronized (optOutLock) { +- // Disable Task, if it is running and the server owner decided to opt-out +- if (isOptOut() && task != null) { +- task.cancel(); +- task = null; +- // Tell all plotters to stop gathering information. +- for (Graph graph : graphs) { +- graph.onOptOut(); +- } +- } +- } +- +- // We use the inverse of firstPost because if it is the first time we are posting, +- // it is not a interval ping, so it evaluates to FALSE +- // Each time thereafter it will evaluate to TRUE, i.e PING! +- postPlugin(!firstPost); +- +- // After the first post we set firstPost to false +- // Each post thereafter will be a ping +- firstPost = false; +- } catch (IOException e) { +- if (debug) { +- Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); +- } +- } +- } +- }, 0, TimeUnit.MINUTES.toMillis(PING_INTERVAL)); +- +- return true; +- } +- } +- +- /** +- * Has the server owner denied plugin metrics? +- * +- * @return true if metrics should be opted out of it +- */ +- public boolean isOptOut() { +- synchronized (optOutLock) { +- try { +- // Reload the metrics file +- configuration.load(getConfigFile()); +- } catch (IOException ex) { +- if (debug) { +- Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); +- } +- return true; +- } catch (InvalidConfigurationException ex) { +- if (debug) { +- Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); +- } +- return true; +- } +- return configuration.getBoolean("opt-out", false); +- } +- } +- +- /** +- * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. +- * +- * @throws java.io.IOException +- */ +- public void enable() throws IOException { +- // This has to be synchronized or it can collide with the check in the task. +- synchronized (optOutLock) { +- // Check if the server owner has already set opt-out, if not, set it. +- if (isOptOut()) { +- configuration.set("opt-out", false); +- configuration.save(configurationFile); +- } +- +- // Enable Task, if it is not running +- if (task == null) { +- start(); +- } +- } +- } +- +- /** +- * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. +- * +- * @throws java.io.IOException +- */ +- public void disable() throws IOException { +- // This has to be synchronized or it can collide with the check in the task. +- synchronized (optOutLock) { +- // Check if the server owner has already set opt-out, if not, set it. +- if (!isOptOut()) { +- configuration.set("opt-out", true); +- configuration.save(configurationFile); +- } +- +- // Disable Task, if it is running +- if (task != null) { +- task.cancel(); +- task = null; +- } +- } +- } +- +- /** +- * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status +- * +- * @return the File object for the config file +- */ +- public File getConfigFile() { +- // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use +- // is to abuse the plugin object we already have +- // plugin.getDataFolder() => base/plugins/PluginA/ +- // pluginsFolder => base/plugins/ +- // The base is not necessarily relative to the startup directory. +- // File pluginsFolder = plugin.getDataFolder().getParentFile(); +- +- // return => base/plugins/PluginMetrics/config.yml +- return new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "PluginMetrics"), "config.yml"); +- } +- +- /** +- * Generic method that posts a plugin to the metrics website +- */ +- private void postPlugin(final boolean isPing) throws IOException { +- // Server software specific section +- String pluginName = "Spigot"; +- boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled +- String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"; +- String serverVersion = Bukkit.getVersion(); +- int playersOnline = Bukkit.getServer().getOnlinePlayers().size(); +- +- // END server software specific section -- all code below does not use any code outside of this class / Java +- +- // Construct the post data +- final StringBuilder data = new StringBuilder(); +- +- // The plugin's description file containg all of the plugin data such as name, version, author, etc +- data.append(encode("guid")).append('=').append(encode(guid)); +- encodeDataPair(data, "version", pluginVersion); +- encodeDataPair(data, "server", serverVersion); +- encodeDataPair(data, "players", Integer.toString(playersOnline)); +- encodeDataPair(data, "revision", String.valueOf(REVISION)); +- +- // New data as of R6 +- String osname = System.getProperty("os.name"); +- String osarch = System.getProperty("os.arch"); +- String osversion = System.getProperty("os.version"); +- String java_version = System.getProperty("java.version"); +- int coreCount = Runtime.getRuntime().availableProcessors(); +- +- // normalize os arch .. amd64 -> x86_64 +- if (osarch.equals("amd64")) { +- osarch = "x86_64"; +- } +- +- encodeDataPair(data, "osname", osname); +- encodeDataPair(data, "osarch", osarch); +- encodeDataPair(data, "osversion", osversion); +- encodeDataPair(data, "cores", Integer.toString(coreCount)); +- encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); +- encodeDataPair(data, "java_version", java_version); +- +- // If we're pinging, append it +- if (isPing) { +- encodeDataPair(data, "ping", "true"); +- } +- +- // Acquire a lock on the graphs, which lets us make the assumption we also lock everything +- // inside of the graph (e.g plotters) +- synchronized (graphs) { +- final Iterator iter = graphs.iterator(); +- +- while (iter.hasNext()) { +- final Graph graph = iter.next(); +- +- for (Plotter plotter : graph.getPlotters()) { +- // The key name to send to the metrics server +- // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top +- // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME +- final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); +- +- // The value to send, which for the foreseeable future is just the string +- // value of plotter.getValue() +- final String value = Integer.toString(plotter.getValue()); +- +- // Add it to the http post data :) +- encodeDataPair(data, key, value); +- } +- } +- } +- +- // Create the url +- URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName))); +- +- // Connect to the website +- URLConnection connection; +- +- // Mineshafter creates a socks proxy, so we can safely bypass it +- // It does not reroute POST requests so we need to go around it +- if (isMineshafterPresent()) { +- connection = url.openConnection(Proxy.NO_PROXY); +- } else { +- connection = url.openConnection(); +- } +- +- connection.setDoOutput(true); +- +- // Write the data +- final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); +- writer.write(data.toString()); +- writer.flush(); +- +- // Now read the response +- final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); +- final String response = reader.readLine(); +- +- // close resources +- writer.close(); +- reader.close(); +- +- if (response == null || response.startsWith("ERR")) { +- throw new IOException(response); //Throw the exception +- } else { +- // Is this the first update this hour? +- if (response.contains("OK This is your first update this hour")) { +- synchronized (graphs) { +- final Iterator iter = graphs.iterator(); +- +- while (iter.hasNext()) { +- final Graph graph = iter.next(); +- +- for (Plotter plotter : graph.getPlotters()) { +- plotter.reset(); +- } +- } +- } +- } +- } +- } +- +- /** +- * Check if mineshafter is present. If it is, we need to bypass it to send POST requests +- * +- * @return true if mineshafter is installed on the server +- */ +- private boolean isMineshafterPresent() { +- try { +- Class.forName("mineshafter.MineServer"); +- return true; +- } catch (Exception e) { +- return false; +- } +- } +- +- /** +- *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair +- * MUST be included manually, e.g:

+- * +- * StringBuffer data = new StringBuffer(); +- * data.append(encode("guid")).append('=').append(encode(guid)); +- * encodeDataPair(data, "version", description.getVersion()); +- * +- * +- * @param buffer the stringbuilder to append the data pair onto +- * @param key the key value +- * @param value the value +- */ +- private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { +- buffer.append('&').append(encode(key)).append('=').append(encode(value)); +- } +- +- /** +- * Encode text as UTF-8 +- * +- * @param text the text to encode +- * @return the encoded text, as UTF-8 +- */ +- private static String encode(final String text) throws UnsupportedEncodingException { +- return URLEncoder.encode(text, "UTF-8"); +- } +- +- /** +- * Represents a custom graph on the website +- */ +- public static class Graph { +- +- /** +- * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is +- * rejected +- */ +- private final String name; +- /** +- * The set of plotters that are contained within this graph +- */ +- private final Set plotters = new LinkedHashSet(); +- +- private Graph(final String name) { +- this.name = name; +- } +- +- /** +- * Gets the graph's name +- * +- * @return the Graph's name +- */ +- public String getName() { +- return name; +- } +- +- /** +- * Add a plotter to the graph, which will be used to plot entries +- * +- * @param plotter the plotter to add to the graph +- */ +- public void addPlotter(final Plotter plotter) { +- plotters.add(plotter); +- } +- +- /** +- * Remove a plotter from the graph +- * +- * @param plotter the plotter to remove from the graph +- */ +- public void removePlotter(final Plotter plotter) { +- plotters.remove(plotter); +- } +- +- /** +- * Gets an unmodifiable set of the plotter objects in the graph +- * +- * @return an unmodifiable {@link java.util.Set} of the plotter objects +- */ +- public Set getPlotters() { +- return Collections.unmodifiableSet(plotters); +- } +- +- @Override +- public int hashCode() { +- return name.hashCode(); +- } +- +- @Override +- public boolean equals(final Object object) { +- if (!(object instanceof Graph)) { +- return false; +- } +- +- final Graph graph = (Graph) object; +- return graph.name.equals(name); +- } +- +- /** +- * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. +- */ +- protected void onOptOut() { +- } +- } +- +- /** +- * Interface used to collect custom data for a plugin +- */ +- public static abstract class Plotter { +- +- /** +- * The plot's name +- */ +- private final String name; +- +- /** +- * Construct a plotter with the default plot name +- */ +- public Plotter() { +- this("Default"); +- } +- +- /** +- * Construct a plotter with a specific plot name +- * +- * @param name the name of the plotter to use, which will show up on the website +- */ +- public Plotter(final String name) { +- this.name = name; +- } +- +- /** +- * Get the current value for the plotted point. Since this function defers to an external function it may or may +- * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called +- * from any thread so care should be taken when accessing resources that need to be synchronized. +- * +- * @return the current value for the point to be plotted. +- */ +- public abstract int getValue(); +- +- /** +- * Get the column name for the plotted point +- * +- * @return the plotted point's column name +- */ +- public String getColumnName() { +- return name; +- } +- +- /** +- * Called after the website graphs have been updated +- */ +- public void reset() { +- } +- +- @Override +- public int hashCode() { +- return getColumnName().hashCode(); +- } +- +- @Override +- public boolean equals(final Object object) { +- if (!(object instanceof Plotter)) { +- return false; +- } +- +- final Plotter plotter = (Plotter) object; +- return plotter.name.equals(name) && plotter.getValue() == getValue(); +- } +- } +-} +\ No newline at end of file +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index fdc64d3..414f8c1 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -48,7 +48,6 @@ public class SpigotConfig + static int version; + static Map commands; + /*========================================================================*/ +- private static Metrics metrics; + + public static void init() + { +@@ -80,18 +79,6 @@ public class SpigotConfig + { + MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() ); + } +- +- if ( metrics == null ) +- { +- try +- { +- metrics = new Metrics(); +- metrics.start(); +- } catch ( IOException ex ) +- { +- Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex ); +- } +- } + } + + static void readConfig(Class clazz, Object instance) +-- +1.9.1 +