From fc0ee28c8c3fe2e5e277f200086a82d79fd616c5 Mon Sep 17 00:00:00 2001 From: Alexander Staeding Date: Mon, 22 Feb 2021 02:09:29 +0100 Subject: [PATCH 1/2] A big cleanup - Simplified commands a lot, new API (breaking) - Command API uses wrapper types now - CommandNode got yeeted - No platform command code needed anymore (for consuming plugins) - Restructured sponge implementation - Started Sponge API8 implementation - Moved a lot of classes to kotlin Use Component instead of TextComponent Added Sponge8KickService and other small fixes Added Usage and Description to the help command output fixed duplicate subcommand alias printing Commands now register properly on Velocity (Sponge needs some work) Signed-off-by: STG-Allen More cleanup, converted AnvilNukkit to Kotlin small stuff More work on Sponge8 Moved to new math lib, more cleanup Removed platform-specific modules Move towards using Adventure where possible Revert "Move towards using Adventure where possible" This reverts commit e7949f99294e31ad72b7648f3da9efd2b8dbbd09. [0.4] Remove TString (#37) * Yeetus deletus 'TString' * Fix minor issues with PaginationService * Cleanup StringTextService * Cleanup Sponge7TextService, fix binding issue with NukkitTextService * Cleanup javadoc * Removed BasicPluginInfo * Fix requested changes * Fix javadoc in TextService * More stuff * More work * Can't forget those pesky bindings * Fix appendJoining not properly appending and joining * Rename our Component to DBComponent for now (until a better name is decided on) to not conflict with kyori's Component * Oops, forgot these * Fix even more stuff * Fix imports * Small final fixes Co-authored-by: Alexander Staeding tmp Convert parts of the api to Kotlin un-get sponge 8 Revert changes to coremember classes, fix some other issues Actually revert indentation in datastore and coremember package oops Remove accidental code gotta remember those imports Mistakes were made --- .../anvil/api/misc/BindingExtensions.kt | 148 ++++++++++++++++++ .../anvil/common/plugin/AnvilPluginInfo.java | 1 + .../common/misc/CommonBindingExtensions.kt | 97 ++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt create mode 100644 anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt diff --git a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt new file mode 100644 index 000000000..5868c908d --- /dev/null +++ b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/misc/BindingExtensions.kt @@ -0,0 +1,148 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.api.misc + +import com.google.common.reflect.TypeToken +import com.google.inject.Key +import com.google.inject.Provider +import com.google.inject.TypeLiteral +import org.anvilpowered.anvil.api.Anvil +import org.anvilpowered.anvil.api.datastore.DBComponent + +@Suppress("unchecked", "UnstableApiUsage") +interface BindingExtensions { + + /** + * Full binding method for a component + * + * A typical example of usage of this method: + * + * be.bind( + * + *   new TypeToken>(getClass()) { + * }, + * + *   new TypeToken>(getClass()) { + * }, + * + *   new TypeToken, Datastore>>(getClass()) { + * }, + * + *   new TypeToken>(getClass()) { // final implementation + * }, + * + *   Names.named("mongodb") + * + * ); + */ + fun , From2 : DBComponent<*, *>, From3 : From1, Target : From1> bind( + from1: TypeToken, + from2: TypeToken, + from3: TypeToken, + target: TypeToken, + componentAnnotation: Annotation, + ) + + /** + * Binding method for a component + * + * + * A typical example of usage of this method: + * + * be.bind( + * + *   new TypeToken>(getClass()) { + * }, + * + *   new TypeToken>(getClass()) { + * }, + * + *   new TypeToken(getClass()) { // final implementation + * }, + *   Names.named("mongodb") + * + * ); + */ + fun , From2 : From1, Target : From1> bind( + from1: TypeToken, + from2: TypeToken, + target: TypeToken, + componentAnnotation: Annotation, + ) + + fun bind( + from: TypeToken, + target: TypeToken, + annotation: Annotation, + ) + + fun bind( + from: TypeToken, + target: TypeToken, + annotation: Class, + ) + + fun bind( + from: TypeToken, + target: TypeToken, + ) + + /** + * Binds the mongodb [DataStoreContext] + * + * Using this method is the same as invoking: + * + * bind(new TypeLiteral>() { + * }).to(MongoContext.class); + */ + fun withMongoDB() + + /** + * Binds the xodus [DataStoreContext] + * + * Using this method is the same as invoking: + * + * binder.bind(new TypeLiteral>() { + * }).to(XodusContext.class); + * + */ + fun withXodus() + + companion object { + fun getTypeLiteral(typeToken: TypeToken): TypeLiteral { + return TypeLiteral.get(typeToken.type) as TypeLiteral + } + + fun getKey(typeToken: TypeToken): Key? { + return Key.get(getTypeLiteral(typeToken)) + } + + fun asInternalProvider(clazz: Class): Provider { + return Provider { Anvil.getEnvironment().injector.getInstance(clazz) } + } + + fun asInternalProvider(typeLiteral: TypeLiteral): Provider { + return Anvil.getEnvironment().injector.getProvider(Key.get(typeLiteral)) + } + + fun asInternalProvider(typeToken: TypeToken): Provider { + return Anvil.getEnvironment().injector.getProvider(getKey(typeToken)) + } + } +} diff --git a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java index 37ffaf4fe..2f5408cb7 100644 --- a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java +++ b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java @@ -21,6 +21,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.anvilpowered.anvil.api.plugin.PluginInfo; +import org.anvilpowered.anvil.api.util.TextService; public class AnvilPluginInfo implements PluginInfo { public static final String id = "anvil"; diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt new file mode 100644 index 000000000..39c1de1f5 --- /dev/null +++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/misc/CommonBindingExtensions.kt @@ -0,0 +1,97 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.common.misc + +import com.google.common.reflect.TypeToken +import com.google.inject.Binder +import com.google.inject.TypeLiteral +import dev.morphia.Datastore +import jetbrains.exodus.entitystore.EntityId +import jetbrains.exodus.entitystore.PersistentEntityStore +import org.anvilpowered.anvil.api.datastore.DBComponent +import org.anvilpowered.anvil.api.datastore.DataStoreContext +import org.anvilpowered.anvil.api.datastore.MongoContext +import org.anvilpowered.anvil.api.datastore.XodusContext +import org.anvilpowered.anvil.api.misc.BindingExtensions +import org.bson.types.ObjectId + +@Suppress("unchecked", "UnstableApiUsage") +class CommonBindingExtensions(val binder: Binder) : BindingExtensions { + + override fun , From2 : DBComponent<*, *>, From3 : From1, Target : From1> bind( + from1: TypeToken, + from2: TypeToken, + from3: TypeToken, + target: TypeToken, + componentAnnotation: Annotation, + ) { + binder.bind(TypeLiteral.get(from2.type) as TypeLiteral) + .annotatedWith(componentAnnotation) + .to(BindingExtensions.getTypeLiteral(target)) + binder.bind(TypeLiteral.get(from3.type) as TypeLiteral) + .to(BindingExtensions.getTypeLiteral(target)) + } + + override fun , From2 : From1, Target : From1> bind( + from1: TypeToken, + from2: TypeToken, + target: TypeToken, + componentAnnotation: Annotation, + ) { + bind(from1, from1, from2, target, componentAnnotation) + } + + override fun bind( + from: TypeToken, + target: TypeToken, + annotation: Annotation, + ) { + binder.bind(BindingExtensions.getTypeLiteral(from)) + .annotatedWith(annotation) + .to(BindingExtensions.getTypeLiteral(target)) + } + + override fun bind( + from: TypeToken, + target: TypeToken, + annotation: Class, + ) { + binder.bind(BindingExtensions.getTypeLiteral(from)) + .annotatedWith(annotation) + .to(BindingExtensions.getTypeLiteral(target)) + } + + override fun bind( + from: TypeToken, + target: TypeToken, + ) { + binder.bind(BindingExtensions.getTypeLiteral(from)) + .to(BindingExtensions.getTypeLiteral(target)) + } + + override fun withMongoDB() { + binder.bind(object : TypeLiteral>() {}) + .to(MongoContext::class.java) + } + + override fun withXodus() { + binder.bind(object : TypeLiteral>() {}) + .to(XodusContext::class.java) + } +} From c95e92541b739c308a1b58d203de9983d418d72b Mon Sep 17 00:00:00 2001 From: STG-Allen Date: Thu, 1 Apr 2021 22:03:54 -0400 Subject: [PATCH 2/2] Implement metric collection --- .../anvil/api/plugin/PluginInfo.kt | 2 + anvil-bungee/build.gradle | 2 + .../bungee/metric/BungeeMetricService.kt | 265 ++++++++++++++++++ anvil-common/build.gradle | 1 + .../anvil/api/EnvironmentBuilderImpl.java | 7 + .../anvil/common/plugin/AnvilPluginInfo.java | 12 +- .../anvil/common/metric/MetricService.kt | 30 ++ .../anvil/common/plugin/FallbackPluginInfo.kt | 3 + .../spigot/metric/SpigotMetricService.kt | 115 ++++++++ .../anvil/spigot/module/ApiSpigotModule.kt | 3 + .../sponge7/metric/Sponge7MetricService.kt | 145 ++++++++++ anvil-sponge/build.gradle | 2 + anvil-velocity/build.gradle | 3 + .../velocity/metric/VelocityMetricService.kt | 186 ++++++++++++ .../velocity/module/ApiVelocityModule.kt | 3 + gradle.properties | 1 + 16 files changed, 779 insertions(+), 1 deletion(-) create mode 100644 anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt create mode 100644 anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt create mode 100644 anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt create mode 100644 anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt create mode 100644 anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt diff --git a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt index 0bf49e49d..72a8c853b 100644 --- a/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt +++ b/anvil-api/src/main/kotlin/org/anvilpowered/anvil/api/plugin/PluginInfo.kt @@ -38,4 +38,6 @@ interface PluginInfo : Named { val organizationName: String val buildDate: String + + val metricIds: Map } diff --git a/anvil-bungee/build.gradle b/anvil-bungee/build.gradle index a771bac64..25e2c46a9 100644 --- a/anvil-bungee/build.gradle +++ b/anvil-bungee/build.gradle @@ -14,6 +14,7 @@ dependencies { } implementation bungee + implementation bstats implementation configurate_hocon implementation javasisst implementation(kotlin_reflect + ":" + kotlin_version) @@ -50,6 +51,7 @@ shadowJar { include dependency(apache_commons) include dependency(aopalliance) include dependency(bson) + include dependency(bstats) include dependency(configurate_core) include dependency(configurate_hocon) include dependency(guice) diff --git a/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt b/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt new file mode 100644 index 000000000..aec035916 --- /dev/null +++ b/anvil-bungee/src/main/kotlin/org/anvilpowered/anvil/bungee/metric/BungeeMetricService.kt @@ -0,0 +1,265 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.bungee.metric + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.inject.Inject +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.ByteArrayOutputStream +import java.io.DataOutputStream +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.io.InputStreamReader +import java.lang.reflect.InvocationTargetException +import java.net.URL +import java.nio.charset.StandardCharsets +import java.util.UUID +import java.util.concurrent.TimeUnit +import java.util.zip.GZIPOutputStream +import javax.net.ssl.HttpsURLConnection +import net.md_5.bungee.api.ProxyServer +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 org.anvilpowered.anvil.api.Environment +import org.anvilpowered.anvil.common.metric.MetricService +import org.slf4j.Logger + +class BungeeMetricService @Inject constructor( + private val logger: Logger +) : MetricService { + + private lateinit var environment: Environment + private var serviceId = 0 + private var enabled = false + private var serverUUID: String? = null + private var logFailedRequests = false + private var logSentData = false + private var logResponseStatusText = false + private val knownMetricsInstances: MutableList = ArrayList() + + override fun initialize(env: Environment) { + val bungeeId: Int? = env.pluginInfo.metricIds["bungeecord"] + requireNotNull(bungeeId) { "Could not find a valid Metrics Id for BungeeCord. Please check your PluginInfo" } + initialize(env, bungeeId) + } + + private fun initialize(environment: Environment, serviceId: Int) { + this.environment = environment + this.serviceId = serviceId + try { + loadConfig() + } catch (e: IOException) { + logger.error("Failed to load bStats config!", e) + return + } + + if (!enabled) { + return + } + + val usedMetricsClass = getFirstBStatsClass() ?: return + if (usedMetricsClass == javaClass) { + linkMetrics(this) + startSubmitting() + } else { + val logMsg = "Failed to link to first metrics class ${usedMetricsClass.name}" + try { + usedMetricsClass.getMethod("linkMetrics", Any::class.java).invoke(null, this) + } catch (e: NoSuchMethodException) { + if (logFailedRequests) { + logger.error(logMsg, e) + } + } catch (e: IllegalAccessException) { + if (logFailedRequests) { + logger.error(logMsg, e) + } + } catch (e: InvocationTargetException) { + if (logFailedRequests) { + logger.error(logMsg, e) + } + } + } + } + + private fun linkMetrics(metrics: Any) = knownMetricsInstances.add(metrics) + + private fun startSubmitting() { + val initialDelay = (1000 * 60 * (3 + Math.random() * 3)).toLong() + val secondDelay = (1000 * 60 * (Math.random() * 30)).toLong() + val plugin = environment.plugin as Plugin + ProxyServer.getInstance().scheduler.schedule(plugin, { submitData() }, initialDelay, TimeUnit.MILLISECONDS) + ProxyServer.getInstance().scheduler.schedule( + plugin, { submitData() }, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS + ) + } + + private fun getServerData(): JsonObject { + val proxyInstance = ProxyServer.getInstance() + val data = JsonObject() + data.addProperty("serverUUID", serverUUID) + data.addProperty("playerAmount", proxyInstance.onlineCount) + data.addProperty("managedServers", proxyInstance.servers.size) + data.addProperty("onlineMode", if (proxyInstance.config.isOnlineMode) 1 else 0) + data.addProperty("bungeecordVersion", proxyInstance.version) + data.addProperty("javaVersion", System.getProperty("java.version")) + data.addProperty("osName", System.getProperty("os.name")) + data.addProperty("osArch", System.getProperty("os.arch")) + data.addProperty("osVersion", System.getProperty("os.version")) + data.addProperty("coreCount", Runtime.getRuntime().availableProcessors()) + return data + } + + private fun submitData() { + val data: JsonObject = getServerData() + val pluginData = JsonArray() + for (metrics in knownMetricsInstances) { + try { + val plugin = metrics.javaClass.getMethod("getPluginData").invoke(metrics) + if (plugin is JsonObject) { + pluginData.add(plugin) + } + } catch (ignored: Exception) { + } + } + data.add("plugins", pluginData) + try { + sendData(data) + } catch (e: Exception) { + if (logFailedRequests) { + logger.error("Could not submit plugin stats!", e) + } + } + } + + @Throws(IOException::class) + private fun loadConfig() { + val bStatsFolder = File("plugins/bStats") + check(bStatsFolder.mkdirs()) { "Could not create the config directory for bStats!" } + val configFile = File(bStatsFolder, "config.yml") + check(configFile.mkdirs()) { "Could not create the config file for bStats!" } + + writeFile( + configFile, + "#bStats collects some data for plugin authors like how many servers are using their plugins.", + "#To honor their work, you should not disable it.", + "#This has nearly no effect on the server performance!", + "#Check out https://bStats.org/ to learn more :)", + "enabled: true", + "serverUuid: \"" + UUID.randomUUID() + "\"", + "logFailedRequests: false", + "logSentData: false", + "logResponseStatusText: false" + ) + + val configuration: Configuration = ConfigurationProvider.getProvider(YamlConfiguration::class.java).load(configFile) + + enabled = configuration.getBoolean("enabled", true) + serverUUID = configuration.getString("serverUuid") + logFailedRequests = configuration.getBoolean("logFailedRequests", false) + logSentData = configuration.getBoolean("logSentData", false) + logResponseStatusText = configuration.getBoolean("logResponseStatusText", false) + } + + private fun getFirstBStatsClass(): Class<*>? { + val bStatsFolder = File("plugins/bStats") + bStatsFolder.mkdirs() + check(bStatsFolder.mkdirs()) { "Could not create the bStats config folder!" } + val tempFile = File(bStatsFolder, "temp.txt") + return try { + val className = readFile(tempFile) + if (className != null) { + try { + return Class.forName(className) + } catch (ignored: ClassNotFoundException) { + } + } + writeFile(tempFile, javaClass.name) + javaClass + } catch (e: IOException) { + if (logFailedRequests) { + logger.error("Failed to get first bStats class!", e) + } + null + } + } + + @Throws(IOException::class) + private fun readFile(file: File): String? { + return if (!file.exists()) { + null + } else { + BufferedReader(FileReader(file)).use { it.readLine() } + } + } + + @Throws(IOException::class) + private fun writeFile(file: File, vararg lines: String) { + BufferedWriter(FileWriter(file)).use { + for (line in lines) { + it.write(line) + it.newLine() + } + } + } + + private fun sendData(data: JsonObject?) { + requireNotNull(data) { "Data cannot be null" } + if (logSentData) { + logger.info("Sending data to bStats: $data") + } + val connection: HttpsURLConnection = URL("https://bStats.org/submitData/bungeecord").openConnection() as HttpsURLConnection + val compressedData = compress(data.toString()) + + connection.requestMethod = "POST" + connection.addRequestProperty("Accept", "application/json") + connection.addRequestProperty("Connection", "close") + connection.addRequestProperty("Content-Encoding", "gzip") + connection.addRequestProperty("Content-Length", compressedData!!.size.toString()) + connection.setRequestProperty("Content-Type", "application/json") + connection.setRequestProperty("User-Agent", "MC-Server/1") + + connection.doOutput = true + DataOutputStream(connection.outputStream).use { outputStream -> outputStream.write(compressedData) } + val builder = StringBuilder() + BufferedReader(InputStreamReader(connection.inputStream)).use { bufferedReader -> + var line: String? + while (bufferedReader.readLine().also { line = it } != null) { + builder.append(line) + } + } + if (logResponseStatusText) { + logger.info("Sent data to bStats and received response: $builder") + } + } + + private fun compress(str: String?): ByteArray? { + if (str == null) { + return null + } + val outputStream = ByteArrayOutputStream() + GZIPOutputStream(outputStream).use { gzip -> gzip.write(str.toByteArray(StandardCharsets.UTF_8)) } + return outputStream.toByteArray() + } +} diff --git a/anvil-common/build.gradle b/anvil-common/build.gradle index 2d2900cf5..93ec9ff74 100644 --- a/anvil-common/build.gradle +++ b/anvil-common/build.gradle @@ -8,6 +8,7 @@ plugins { dependencies { api(project(':anvil-api')) + api(bstats) api("net.kyori:adventure-text-serializer-legacy:4.5.0") api("net.kyori:adventure-text-serializer-plain:4.5.0") api(configurate_hocon) diff --git a/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java b/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java index c0c42ccf9..3f7e044fb 100644 --- a/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java +++ b/anvil-common/src/main/java/org/anvilpowered/anvil/api/EnvironmentBuilderImpl.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import com.google.common.reflect.TypeToken; import com.google.inject.AbstractModule; +import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; @@ -32,6 +33,7 @@ import org.anvilpowered.anvil.api.registry.Registry; import org.anvilpowered.anvil.api.registry.RegistryScope; import org.anvilpowered.anvil.common.PlatformImpl; +import org.anvilpowered.anvil.common.metric.MetricService; import org.anvilpowered.anvil.common.module.PlatformModule; import org.checkerframework.checker.nullness.qual.Nullable; @@ -132,6 +134,11 @@ protected void configure() { } ServiceManagerImpl.environmentManager .registerEnvironment(environment, environment.getPlugin()); + Binding metricBinding = + Anvil.environment.getInjector().getExistingBinding(Key.get(MetricService.class)); + if (metricBinding != null) { + metricBinding.getProvider().get().initialize(environment); + } for (Map.Entry, Consumer> entry : environment.getEarlyServices().entrySet()) { ((Consumer) entry.getValue()) diff --git a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java index 2f5408cb7..814f31e17 100644 --- a/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java +++ b/anvil-common/src/main/java/org/anvilpowered/anvil/common/plugin/AnvilPluginInfo.java @@ -18,12 +18,16 @@ package org.anvilpowered.anvil.common.plugin; +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.anvilpowered.anvil.api.plugin.PluginInfo; import org.anvilpowered.anvil.api.util.TextService; -public class AnvilPluginInfo implements PluginInfo { +import java.util.Map; + +public class AnvilPluginInfo implements PluginInfo { public static final String id = "anvil"; public static final String name = "Anvil"; public static final String version = "$modVersion"; @@ -37,6 +41,7 @@ public class AnvilPluginInfo implements PluginInfo { .append(Component.text(name, NamedTextColor.AQUA)) .append(Component.text("] ", NamedTextColor.BLUE)) .build(); + public static final Map metricId = ImmutableMap.of(); @Override public String getId() { @@ -82,4 +87,9 @@ public String getBuildDate() { public Component getPrefix() { return pluginPrefix; } + + @Override + public Map getMetricIds() { + return metricId; + } } diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt new file mode 100644 index 000000000..2c80e8b6d --- /dev/null +++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/metric/MetricService.kt @@ -0,0 +1,30 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.common.metric + +import org.anvilpowered.anvil.api.Environment + +interface MetricService { + + /** + * Initializes metrics through bStats for the specified [Environment] + */ + fun initialize(env: Environment) + +} diff --git a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt index 00348462e..144d8113e 100644 --- a/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt +++ b/anvil-common/src/main/kotlin/org/anvilpowered/anvil/common/plugin/FallbackPluginInfo.kt @@ -18,6 +18,7 @@ package org.anvilpowered.anvil.common.plugin +import com.google.common.collect.ImmutableMap import com.google.inject.Inject import net.kyori.adventure.text.Component import org.anvilpowered.anvil.api.Environment @@ -32,6 +33,7 @@ class FallbackPluginInfo : PluginInfo { val authors = arrayOf("author") const val organizationName = "organizationName" const val buildDate = "last night" + val metricIds: Map = ImmutableMap.of("fake", 0) } @Inject @@ -53,4 +55,5 @@ class FallbackPluginInfo : PluginInfo { override val organizationName: String = Companion.organizationName override val buildDate: String = Companion.buildDate override val prefix: Component = pluginPrefix + override val metricIds: Map = Companion.metricIds } diff --git a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt new file mode 100644 index 000000000..af0ce8120 --- /dev/null +++ b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/metric/SpigotMetricService.kt @@ -0,0 +1,115 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.spigot.metric + +import com.google.inject.Inject +import java.io.File +import java.io.IOException +import java.util.UUID +import org.anvilpowered.anvil.api.Environment +import org.anvilpowered.anvil.api.util.UserService +import org.anvilpowered.anvil.common.metric.MetricService +import org.bstats.MetricsBase +import org.bstats.json.JsonObjectBuilder +import org.bukkit.Bukkit +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import org.slf4j.Logger + +class SpigotMetricService @Inject constructor( + private val logger: Logger, + private val userService: UserService +) : MetricService { + + private lateinit var environment: Environment + private var metricsBase: MetricsBase? = null + + override fun initialize(env: Environment) { + val serviceId: Int? = env.pluginInfo.metricIds["spigot"] + checkNotNull(serviceId) { "Could not find a valid Metrics Id for Spigot. Please check your PluginInfo" } + initialize(env, serviceId) + } + + private fun initialize(environment: Environment, serviceId: Int) { + this.environment = environment + val bStatsFolder = File("plugins/bStats") + val configFile = File(bStatsFolder, "config.yml") + if (!bStatsFolder.exists()) { + require(configFile.mkdirs()) { "Could not create the bStats config!" } + } + val config: YamlConfiguration = YamlConfiguration.loadConfiguration(configFile) + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true) + config.addDefault("serverUuid", UUID.randomUUID().toString()) + config.addDefault("logFailedRequests", false) + config.addDefault("logSentData", false) + config.addDefault("logResponseStatusText", false) + + config.options().header( + """ + bStats collects some data for plugin authors like how many servers are using their plugins.\n + To honor their work, you should not disable it.\n + This has nearly no effect on the server performance!\n + Check out https://bStats.org/ to learn more :) + """.trimIndent() + ).copyDefaults(true) + try { + config.save(configFile) + } catch (ignored: IOException) { + } + } + + val enabled: Boolean = config.getBoolean("enabled", true) + val serverUUID: String = config.getString("serverUuid")!! + val logErrors: Boolean = config.getBoolean("logFailedRequests", false) + val logSentData: Boolean = config.getBoolean("logSentData", false) + val logResponseStatusText: Boolean = config.getBoolean("logResponseStatusText", false) + metricsBase = MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + { appendPlatformData(it) }, + { appendServiceData(it) }, + { Bukkit.getScheduler().runTask(environment.plugin as Plugin, it) }, + { (environment.plugin as Plugin).isEnabled }, + { message: String, error: Throwable -> logger.error(message, error) }, + { logger.info(it) }, + logErrors, + logSentData, + logResponseStatusText + ) + } + + private fun appendPlatformData(builder: JsonObjectBuilder) { + builder.appendField("playerAmount", getPlayerAmount()) + builder.appendField("onlineMode", if (Bukkit.getOnlineMode()) 1 else 0) + builder.appendField("bukkitVersion", Bukkit.getVersion()) + builder.appendField("bukkitName", Bukkit.getName()) + 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 fun appendServiceData(builder: JsonObjectBuilder) = builder.appendField("pluginVersion", environment.pluginInfo.version) + private fun getPlayerAmount(): Int = userService.onlinePlayers.size +} diff --git a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt index 4e0eaa3a9..6d6e5c2fe 100644 --- a/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt +++ b/anvil-spigot/src/main/kotlin/org/anvilpowered/anvil/spigot/module/ApiSpigotModule.kt @@ -27,6 +27,7 @@ import org.anvilpowered.anvil.api.util.TextService import org.anvilpowered.anvil.api.util.UserService import org.anvilpowered.anvil.common.PlatformImpl import org.anvilpowered.anvil.common.entity.EntityUtils +import org.anvilpowered.anvil.common.metric.MetricService import org.anvilpowered.anvil.common.module.JavaUtilLoggingAdapter import org.anvilpowered.anvil.common.module.PlatformModule import org.anvilpowered.anvil.common.util.CommonTextService @@ -34,6 +35,7 @@ import org.anvilpowered.anvil.common.util.SendTextService import org.anvilpowered.anvil.spigot.command.SpigotCommandExecuteService import org.anvilpowered.anvil.spigot.command.SpigotSimpleCommandService import org.anvilpowered.anvil.spigot.entity.SpigotEntityUtils +import org.anvilpowered.anvil.spigot.metric.SpigotMetricService import org.anvilpowered.anvil.spigot.server.SpigotLocationService import org.anvilpowered.anvil.spigot.util.SpigotKickService import org.anvilpowered.anvil.spigot.util.SpigotPermissionService @@ -58,6 +60,7 @@ class ApiSpigotModule : PlatformModule( bind(KickService::class.java).to(SpigotKickService::class.java) bind(EntityUtils::class.java).to(SpigotEntityUtils::class.java) bind(LocationService::class.java).to(SpigotLocationService::class.java) + bind(MetricService::class.java).to(SpigotMetricService::class.java) bind(PermissionService::class.java).to(SpigotPermissionService::class.java) bind(object : TypeLiteral>() {}).to(SpigotSendTextService::class.java) bind(object : TypeLiteral>() {}).to(object : TypeLiteral>() {}) diff --git a/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt b/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt new file mode 100644 index 000000000..d7b7d1705 --- /dev/null +++ b/anvil-sponge/anvil-sponge-7/src/main/kotlin/org/anvilpowered/anvil/sponge7/metric/Sponge7MetricService.kt @@ -0,0 +1,145 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.sponge7.metric + +import com.google.inject.Inject +import java.io.File +import java.io.IOException +import java.nio.file.Path +import java.util.UUID +import ninja.leaping.configurate.commented.CommentedConfigurationNode +import ninja.leaping.configurate.hocon.HoconConfigurationLoader +import org.anvilpowered.anvil.api.Environment +import org.anvilpowered.anvil.common.metric.MetricService +import org.bstats.MetricsBase +import org.bstats.json.JsonObjectBuilder +import org.slf4j.Logger +import org.spongepowered.api.Platform +import org.spongepowered.api.Sponge +import org.spongepowered.api.plugin.PluginContainer +import org.spongepowered.api.scheduler.Scheduler +import org.spongepowered.api.scheduler.Task + +class Sponge7MetricService @Inject constructor( + private val logger: Logger +) : MetricService { + + private lateinit var plugin: PluginContainer + private lateinit var configDir: Path + private var serviceId = 0 + private lateinit var metricsBase: MetricsBase + private var serverUUID: String? = null + private var logErrors = false + private var logSentData = false + private var logResponseStatusText = false + + override fun initialize(env: Environment) { + val serviceId: Int? = env.pluginInfo.metricIds["sponge"] + checkNotNull(serviceId) { "Could not find a valid Metrics Id for Sponge. Please check your PluginInfo" } + + this.serviceId = serviceId + this.configDir = Sponge.getConfigManager().getSharedConfig(env.plugin).configPath + try { + loadConfig() + } catch (e: IOException) { + logger.warn("Failed to load bStats config!", e) + return + } + this.plugin = env.injector.getInstance(PluginContainer::class.java) + metricsBase = MetricsBase( + "sponge", + serverUUID, + serviceId, + Sponge.getMetricsConfigManager().getCollectionState(plugin).asBoolean(), + { builder: JsonObjectBuilder -> appendPlatformData(builder) }, + { builder: JsonObjectBuilder -> appendServiceData(builder) }, + { task: Runnable -> + val scheduler: Scheduler = Sponge.getScheduler() + val taskBuilder: Task.Builder = scheduler.createTaskBuilder() + taskBuilder.execute(task).submit(plugin) + }, + { true }, + logger::warn, + logger::info, + logErrors, + logSentData, + logResponseStatusText + ) + val builder = StringBuilder() + builder.append("Plugin ").append(plugin.name).append(" is using bStats Metrics ") + if (Sponge.getMetricsConfigManager().getCollectionState(plugin).asBoolean()) { + builder.append(" and is allowed to send data.") + } else { + builder.append(" but currently has data sending disabled.").append(System.lineSeparator()) + builder.append("To change the enabled/disabled state of any bStats use in a plugin, visit the Sponge config!") + } + logger.info(builder.toString()) + } + + private fun loadConfig() { + val configPath: File = configDir.resolve("bStats").toFile() + if (!configPath.exists()) { + if (!configPath.mkdirs()) { + logger.error("Could not create the bStats directory!") + } + } + + val configFile = File(configPath, "config.conf") + val configurationLoader: HoconConfigurationLoader = + HoconConfigurationLoader.builder().setFile(configFile).build() + + if (!configFile.exists()) { + require(configFile.mkdirs()) { "Could not create the bStats config!" } + } + + val node: CommentedConfigurationNode = configurationLoader.load() + node.getNode("serverUuid").value = UUID.randomUUID().toString() + node.getNode("logFailedRequests").value = false + node.getNode("logSentData").value = false + node.getNode("logResponseStatusText").value = false + node.getNode("serverUuid").setComment( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To control whether this is enabled or disabled, see the Sponge configuration file.\n" + + "Check out https://bStats.org/ to learn more :)" + ) + node.getNode("configVersion").value = 2 + configurationLoader.save(node) + + serverUUID = node.getNode("serverUuid").string + logErrors = node.getNode("logFailedRequests").getBoolean(false) + logSentData = node.getNode("logSentData").getBoolean(false) + logResponseStatusText = node.getNode("logResponseStatusText").getBoolean(false) + } + + private fun appendPlatformData(builder: JsonObjectBuilder) { + builder.appendField("playerAmount", Sponge.getServer().onlinePlayers.size) + builder.appendField("onlineMode", if (Sponge.getServer().onlineMode) 1 else 0) + builder.appendField("minecraftVersion", Sponge.getGame().platform.minecraftVersion.name) + builder.appendField("spongeImplementation", Sponge.getPlatform().getContainer(Platform.Component.IMPLEMENTATION).name) + 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 fun appendServiceData(builder: JsonObjectBuilder) { + builder.appendField("pluginVersion", plugin.version.orElse("unknown")) + } +} diff --git a/anvil-sponge/build.gradle b/anvil-sponge/build.gradle index 5a136b397..fdeffceef 100644 --- a/anvil-sponge/build.gradle +++ b/anvil-sponge/build.gradle @@ -43,9 +43,11 @@ shadowJar { exclude("META-INF/versions/**") relocate("org.apache.commons", "relocated.apache") + relocate("org.bstats", "relocated.org.bstats") relocate("ninja.leaping", "relocated") include dependency(apache_commons) include dependency(bson) + include dependency(bstats) include dependency(configurate_core) include dependency(configurate_hocon) include dependency(javasisst) diff --git a/anvil-velocity/build.gradle b/anvil-velocity/build.gradle index 9439a75d7..726d48e8e 100644 --- a/anvil-velocity/build.gradle +++ b/anvil-velocity/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation project(':Anvil:anvil-common') } + implementation bstats implementation javasisst implementation(kotlin_reflect + ":" + kotlin_version) implementation(kotlin_stdlib + ":" + kotlin_version) @@ -45,8 +46,10 @@ shadowJar { include project(':Anvil:anvil-common') } + relocate("org.bstats", "relocated.org.bstats") include dependency(apache_commons) include dependency(bson) + include dependency(bstats) include dependency(javasisst) include dependency(jedis) include dependency(kotlin_reflect) diff --git a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt new file mode 100644 index 000000000..ce3449317 --- /dev/null +++ b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/metric/VelocityMetricService.kt @@ -0,0 +1,186 @@ +/* + * Anvil - AnvilPowered + * Copyright (C) 2020-2021 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package org.anvilpowered.anvil.velocity.metric + +import com.google.inject.Inject +import com.velocitypowered.api.proxy.ProxyServer +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Optional +import java.util.UUID +import java.util.regex.Pattern +import java.util.stream.Collectors +import org.anvilpowered.anvil.api.Environment +import org.anvilpowered.anvil.api.Platform +import org.anvilpowered.anvil.api.server.LocationService +import org.anvilpowered.anvil.common.metric.MetricService +import org.bstats.MetricsBase +import org.bstats.json.JsonObjectBuilder +import org.slf4j.Logger + +class VelocityMetricService @Inject constructor( + private val logger: Logger, + private val platform: Platform, + private val proxyServer: ProxyServer, + private val locationService: LocationService, +) : MetricService { + + private lateinit var metricsBase: MetricsBase + private var serverUUID: String? = null + private var enabled: Boolean = false + private var logErrors: Boolean = false + private var logSentData: Boolean = false + private var logResponseStatusText: Boolean = false + private lateinit var dataDirectory: Path + private lateinit var environment: Environment + + override fun initialize(env: Environment) { + val serviceId: Int? = env.pluginInfo.metricIds["velocity"] + requireNotNull(serviceId) { "Could not find a valid Metrics Id for Velocity. Please check your PluginInfo" } + initialize(env, Paths.get("plugins/bStats"), serviceId) + } + + private fun initialize(environment: Environment, dataPath: Path, serviceId: Int) { + this.dataDirectory = dataPath + this.environment = environment + try { + setupConfig(true) + } catch (e: IOException) { + logger.error("Failed to create bStats config", e) + } + metricsBase = MetricsBase( + platform.name, + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + { task -> proxyServer.scheduler.buildTask(environment.plugin, task).schedule() }, + { true }, + logger::warn, + logger::info, + logErrors, + logSentData, + logResponseStatusText + ) + } + + private fun setupConfig(recreateWhenMalformed: Boolean) { + val configDir = dataDirectory.parent.resolve("bStats").toFile() + if (!configDir.exists()) { + require(configDir.mkdirs()) {"Could not create the bStats config!"} + } + val configFile = File(configDir, "config.txt") + if (!configFile.exists()) { + writeConfig(configFile) + } + + val lines: List = readFile(configFile) + + enabled = getConfigValue("enabled", lines).map { anObject: String -> "true" == anObject }.orElse(true) + serverUUID = getConfigValue("server-uuid", lines).orElse(null) + logErrors = getConfigValue("log-errors", lines).map { anObject: String -> "true" == anObject }.orElse(false) + logSentData = getConfigValue("log-sent-data", lines).map { anObject: String -> "true" == anObject }.orElse(false) + logResponseStatusText = getConfigValue("log-response-status-text", lines) + .map { anObject: String -> "true" == anObject }.orElse(false) + + if (serverUUID == null) { + if (recreateWhenMalformed) { + logger.info("Found malformed bStats config file. Re-creating it...") + if (!configFile.delete()) { + logger.error("Could not delete the bStats config!") + return + } + setupConfig(false) + } else { + logger.error("Failed to re-create malformed bStats config file") + return + } + } + } + + private fun writeConfig(file: File) { + val configContent: MutableList = ArrayList() + configContent.add("# bStats collects some basic information for plugin authors, like how many people use") + configContent.add("# their plugin and their total player count. It's recommend to keep bStats enabled, but") + configContent.add("# if you're not comfortable with this, you can turn this setting off. There is no") + configContent.add("# performance penalty associated with having metrics enabled, and data sent to bStats") + configContent.add("# can't identify your server.") + configContent.add("enabled=true") + configContent.add("server-uuid=" + UUID.randomUUID().toString()) + configContent.add("log-errors=false") + configContent.add("log-sent-data=false") + configContent.add("log-response-status-text=false") + writeFile(file, configContent) + } + + private fun getConfigValue(key: String, lines: List): Optional { + return lines.stream() + .filter { it.startsWith("$key=") } + .map { it.replaceFirst(Pattern.quote("$key=").toRegex(), "") } + .findFirst() + } + + private fun readFile(file: File): List { + if (!file.exists()) { + return emptyList() + } + FileReader(file).use { BufferedReader(it).use { o -> return o.lines().collect(Collectors.toList()) } } + } + + private fun writeFile(file: File, lines: List) { + if (!file.exists()) { + if (!file.createNewFile()) { + logger.error("Could not create the config file for bStats!") + return + } + } + FileWriter(file).use { + BufferedWriter(it).use { o -> + for (line in lines) { + o.write(line) + o.newLine() + } + } + } + } + + private fun appendPlatformData(builder: JsonObjectBuilder) { + builder.appendField("playerAmount", proxyServer.playerCount) + builder.appendField("managedServers", locationService.getServers().size) + builder.appendField("onlineMode", if (proxyServer.configuration.isOnlineMode) 1 else 0) + builder.appendField("velocityVersionVersion", proxyServer.version.version) + builder.appendField("velocityVersionName", proxyServer.version.name) + builder.appendField("velocityVersionVendor", proxyServer.version.vendor) + 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 fun appendServiceData(builder: JsonObjectBuilder) = + builder.appendField("pluginVersion", environment.pluginInfo.version) +} diff --git a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt index a0e3e0963..86414a6cf 100644 --- a/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt +++ b/anvil-velocity/src/main/kotlin/org/anvilpowered/anvil/velocity/module/ApiVelocityModule.kt @@ -31,11 +31,13 @@ import org.anvilpowered.anvil.api.util.TextService import org.anvilpowered.anvil.api.util.UserService import org.anvilpowered.anvil.common.PlatformImpl import org.anvilpowered.anvil.common.command.CommonCallbackCommand +import org.anvilpowered.anvil.common.metric.MetricService import org.anvilpowered.anvil.common.module.PlatformModule import org.anvilpowered.anvil.common.util.CommonTextService import org.anvilpowered.anvil.common.util.SendTextService import org.anvilpowered.anvil.velocity.command.VelocityCommandExecuteService import org.anvilpowered.anvil.velocity.command.VelocitySimpleCommandService +import org.anvilpowered.anvil.velocity.metric.VelocityMetricService import org.anvilpowered.anvil.velocity.server.VelocityLocationService import org.anvilpowered.anvil.velocity.util.VelocityKickService import org.anvilpowered.anvil.velocity.util.VelocityPermissionService @@ -57,6 +59,7 @@ class ApiVelocityModule : PlatformModule( bind>().to() bind().to() bind().to() + bind().to() bind().to() bind>().to>() bind>().to>() diff --git a/gradle.properties b/gradle.properties index 70fd21b0a..f57556bae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ aopalliance=aopalliance:aopalliance:1.0 apache_commons=org.apache.commons:commons-pool2:2.6.2 bson=org.mongodb:bson:3.12.0 +bstats=org.bstats:bstats-base:2.1.0 bungee=net.md-5:bungeecord-api:1.15-SNAPSHOT configurate_core=org.spongepowered:configurate-core:3.7.2 configurate_hocon=org.spongepowered:configurate-hocon:3.7.2