package net.obnoxint.mcdev.omclib.metrics; 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.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.LogRecord; import java.util.logging.Logger; import net.obnoxint.mcdev.omclib.OmcLibPlugin; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; final class MetricsAdapter implements Runnable { private static final int REVISION = 5; private static final String REPORT_URL = "http://mcstats.org/report/"; private static final String CUSTOM_DATA_SEPARATOR = "~~"; private static final String URL_ENCODING = "UTF-8"; private static final String RESPONSE_ERROR = "ERR"; private static final String RESPONSE_OK = "OK This is your first update this hour"; private static final String SEP = "="; private static final String VAL_SEP = "&"; private final OmcLibMetricsFeature feature; private final String guid; private final String serverVersion; private final Set<String> alreadyReported = new HashSet<>(); private long lastReport = Long.MIN_VALUE; private Logger dumpLogger = null; MetricsAdapter(final OmcLibMetricsFeature feature) { this.feature = feature; this.guid = OmcLibPlugin.getInstance().getUidProvider().getUID(feature, true).toString(); this.serverVersion = Bukkit.getVersion(); } @Override public void run() { try { report(false); } catch (final Exception e) { feature.getFeaturePlugin().getLogger().severe(e.getMessage()); } } String getGuid() { return guid; } long getLastReportDate() { return lastReport; } void report(final boolean force) throws Exception { if (!feature.isFeatureActive() || !feature.getFeatureProperties().isAllowReport() || (!force && lastReport > Long.MIN_VALUE && ((System.currentTimeMillis() - lastReport) / 60000) < feature.getFeatureProperties().getReportIntervalMinutes())) { return; } final Set<MetricsInstance> instances = new HashSet<>(); synchronized (feature) { final Map<String, MetricsInstance> m = feature.getMetricsInstances(); for (final String s : m.keySet()) { instances.add(new MetricsInstance(m.get(s))); } } for (final MetricsInstance mi : instances) { final String n = mi.getPluginName(); final Plugin p = Bukkit.getPluginManager().getPlugin(n); if (p != null && p.isEnabled()) { // don't let the adapter send data for disabled plugins // declare necessary fields final URL url; final URLConnection urlConnection; final OutputStreamWriter osw; final BufferedReader br; final String response; // declare the StringBuilder which contains the URL data and fill it with the basic data final StringBuilder urlData = new StringBuilder(toUtf8("guid")).append(SEP).append(toUtf8(guid)) .append(VAL_SEP).append(toUtf8("version")).append(SEP).append(toUtf8(p.getDescription().getVersion())) .append(VAL_SEP).append(toUtf8("server")).append(SEP).append(toUtf8(serverVersion)) .append(VAL_SEP).append(toUtf8("players")).append(SEP).append(toUtf8(Integer.toString(Bukkit.getServer().getOnlinePlayers().length))) .append(VAL_SEP).append(toUtf8("revision")).append(SEP).append(toUtf8(Integer.toString(REVISION))); // imitate Hidendras Metrics ping behaviour if (alreadyReported.contains(n)) { urlData.append(VAL_SEP).append(toUtf8("ping")).append(SEP).append(toUtf8("true")); } else { alreadyReported.add(n); } // add the plotter data to the data for (final String gn : mi.getGraphNames()) { for (final String pid : mi.getGraph(gn).getPlotterIds()) { final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, gn, CUSTOM_DATA_SEPARATOR, pid); final String value = Long.toString(mi.getGraph(gn).getPlotter(pid).getBalance()); // TODO: WARNING! Hidendras Metrics might not support Long values! urlData.append(VAL_SEP).append(toUtf8(key)).append(SEP).append(toUtf8(value)); } } // get the stuff kicked out in the world url = new URL(REPORT_URL + toUtf8(n)); urlConnection = getConnection(url); urlConnection.setDoOutput(true); osw = new OutputStreamWriter(urlConnection.getOutputStream()); osw.write(urlData.toString()); osw.flush(); osw.close(); // what does the world say about it? br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); response = br.readLine(); br.close(); if (response == null || response.startsWith(RESPONSE_ERROR)) { throw new IOException(response); } else if (response.contains(RESPONSE_OK)) { // reset the plotters and put them back in the instance final Set<MetricsGraph> graphs = new HashSet<>(); for (final String gn : mi.getGraphNames()) { final MetricsGraph graph = new MetricsGraph(mi.getGraph(gn)); for (final String pid : graph.getPlotterIds()) { final MetricsPlotter plotter = new MetricsPlotter(graph.getPlotter(pid)); if (plotter.isAutoReset()) { plotter.reset(); graph.updatePlotter(plotter); } } graphs.add(graph); } feature.getMetricsInstance(p).putGraphs(graphs.toArray(new MetricsGraph[graphs.size()])); } if (feature.getFeatureProperties().isCreateReportDump()) { // create a dump log if the user wants to getDumpLogger().info("Metrics data sent: " + urlData.toString()); } } } // we're done! lastReport = System.currentTimeMillis(); } private URLConnection getConnection(final URL url) throws IOException { try { Class.forName("mineshafter.MineServer"); return url.openConnection(Proxy.NO_PROXY); } catch (final Exception e) { return url.openConnection(); } } private Logger getDumpLogger() { if (dumpLogger == null) { dumpLogger = Logger.getAnonymousLogger(); try { final FileHandler fh = new FileHandler(new File(feature.getFeatureFolder(), "report.txt").getAbsolutePath()); fh.setFormatter(new Formatter() { @Override public String format(final LogRecord record) { return record.getMessage() + "\n"; } }); dumpLogger.addHandler(fh); } catch (SecurityException | IOException e) { e.printStackTrace(); } } return dumpLogger; } private String toUtf8(final String string) throws UnsupportedEncodingException { return URLEncoder.encode(string, URL_ENCODING); } }