Compare commits
181 Commits
Author | SHA1 | Date |
---|---|---|
AuroraLS3 | c8fee67f10 | |
Aurora Lahtela | bea0ef85b3 | |
Aurora Lahtela | 8309f8725e | |
dependabot[bot] | 8e6befc953 | |
Aurora Lahtela | 41ae9a3a70 | |
dependabot[bot] | 18a154e6b0 | |
dependabot[bot] | 3e30340aa8 | |
dependabot[bot] | 4b67a89d8c | |
dependabot[bot] | c5513cd5db | |
dependabot[bot] | cde66f1981 | |
dependabot[bot] | 4481c6411e | |
dependabot[bot] | 99d22185bb | |
dependabot[bot] | 26fd2992a8 | |
dependabot[bot] | 5472144f74 | |
dependabot[bot] | dddb9186b2 | |
dependabot[bot] | c5f811f99e | |
dependabot[bot] | b2deda8a28 | |
Aurora Lahtela | edb871661c | |
Aurora Lahtela | ad98e28e4c | |
Aurora Lahtela | 365ea2d333 | |
张宇衡 | fefbd9ae23 | |
dependabot[bot] | 5e0780be1a | |
Aurora Lahtela | 3dc6ac5afa | |
Aurora Lahtela | 8a16455e95 | |
Aurora Lahtela | de75d3c56f | |
Aurora Lahtela | 7de0464137 | |
jhqwqmc | 8b2f32bb7d | |
Aurora Lahtela | ca9870d9fe | |
Aurora Lahtela | 9e25f2b26c | |
Aurora Lahtela | 24a8c75b67 | |
Sniper_TVmc | 132fa2f919 | |
Aurora Lahtela | 7f268d9a07 | |
Aurora Lahtela | be8c8951c9 | |
AuroraLS3 | e630db1b71 | |
Aurora Lahtela | 0d5d9adb77 | |
jhqwqmc | 590e0445cb | |
Aurora Lahtela | bd4108a367 | |
Drex | faa911e232 | |
dependabot[bot] | 6776c79a14 | |
dependabot[bot] | 13d168a593 | |
Aurora Lahtela | 634d6f2a77 | |
Aurora Lahtela | 74ae2dcf40 | |
Aurora Lahtela | f40e1498c1 | |
dependabot[bot] | b6f68936cf | |
dependabot[bot] | 55799eafa0 | |
Aurora Lahtela | 8c36f318c7 | |
Aurora Lahtela | 49269d3aab | |
dependabot[bot] | 95a20b54a3 | |
dependabot[bot] | 2b9314a104 | |
dependabot[bot] | 5a80c6482b | |
dependabot[bot] | 92f887b4a0 | |
dependabot[bot] | 5af89eb787 | |
dependabot[bot] | d5fc213523 | |
dependabot[bot] | e4a43af3ef | |
dependabot[bot] | a543af9562 | |
dependabot[bot] | 9a605994e8 | |
dependabot[bot] | aac41547db | |
Aurora Lahtela | 6d9494d680 | |
Aurora Lahtela | b800a9b3ee | |
jhqwqmc | bd8d92b45c | |
Aurora Lahtela | 4fb67d7ba7 | |
Aurora Lahtela | a4cd8257e2 | |
Aurora Lahtela | 20b8ab9baa | |
AuroraLS3 | e021162729 | |
Aurora Lahtela | 7463d4e440 | |
Aurora Lahtela | 9fa1a94301 | |
jhqwqmc | 30532acf46 | |
AuroraLS3 | 4617876e44 | |
Aurora Lahtela | 252832fcf6 | |
Aurora Lahtela | 8116063e62 | |
Aurora Lahtela | 24e6af2d03 | |
Aurora Lahtela | 7299e10064 | |
Aurora Lahtela | de9f9ec5b4 | |
Aurora Lahtela | 670ef2aff3 | |
Aurora Lahtela | 9c43287f60 | |
Aurora Lahtela | 9ade3fbf01 | |
Aurora Lahtela | 3aa8a71501 | |
dependabot[bot] | 9e29b5aa6b | |
dependabot[bot] | 94c13f38e4 | |
dependabot[bot] | b244d43635 | |
dependabot[bot] | 05a61ec75a | |
dependabot[bot] | 51d31daa81 | |
dependabot[bot] | 0c2b4e1b3e | |
dependabot[bot] | 520adf6ca8 | |
dependabot[bot] | 1a0f0f33a7 | |
Aurora Lahtela | 1beb1ec4bd | |
Aurora Lahtela | 70e3f394ba | |
AuroraLS3 | e1b4e34e77 | |
dependabot[bot] | a9ab23347a | |
dependabot[bot] | c764b2cb31 | |
dependabot[bot] | e10185f344 | |
dependabot[bot] | bdbd403e3b | |
dependabot[bot] | 2c7813e795 | |
dependabot[bot] | 0467c0ddd1 | |
dependabot[bot] | e33033808c | |
Aurora Lahtela | 5044b6e365 | |
Aurora Lahtela | 12cf9ea414 | |
Aurora Lahtela | 701866cc6a | |
Aurora Lahtela | 7368eccbbd | |
dependabot[bot] | 3ddfe6b166 | |
dependabot[bot] | 68140ba7f9 | |
dependabot[bot] | b03a4be38b | |
dependabot[bot] | d4aa1e254d | |
dependabot[bot] | 7fd7f704bf | |
dependabot[bot] | 2cfd0bff2f | |
dependabot[bot] | f2749a33ff | |
dependabot[bot] | f69a2858ee | |
dependabot[bot] | f78dca306c | |
dependabot[bot] | 8861e99abc | |
dependabot[bot] | 34013901c7 | |
dependabot[bot] | a2147f2fab | |
Aurora Lahtela | 367d8af59a | |
Aurora Lahtela | 6ed23f0c0b | |
Aurora Lahtela | 4042980379 | |
Aurora Lahtela | 092533d0b7 | |
Aurora Lahtela | ab94ab9125 | |
Aurora Lahtela | b50fa10e12 | |
dependabot[bot] | a3a2085c8a | |
dependabot[bot] | 8170f4f974 | |
dependabot[bot] | d15cf82e9c | |
dependabot[bot] | b1566dfe80 | |
dependabot[bot] | 1297ede72d | |
dependabot[bot] | f8553009f3 | |
dependabot[bot] | 7d3ab667d8 | |
dependabot[bot] | a4e2f0d6f7 | |
dependabot[bot] | e955f80daa | |
dependabot[bot] | aad3073c92 | |
Aurora Lahtela | b867bcebdb | |
Aurora Lahtela | bede36957b | |
Aurora Lahtela | 2daf3943b7 | |
Aurora Lahtela | e041e193fc | |
Aurora Lahtela | bf3bdb599d | |
dependabot[bot] | 8ced48d815 | |
dependabot[bot] | 45833d5fc7 | |
dependabot[bot] | ad8256cf04 | |
dependabot[bot] | 0ccafd9050 | |
dependabot[bot] | 60f2aa6c42 | |
dependabot[bot] | 468bd32111 | |
dependabot[bot] | ed543e460e | |
dependabot[bot] | cac627a2f7 | |
dependabot[bot] | a0a430507e | |
Aurora Lahtela | 5ddbd52d37 | |
Aurora Lahtela | 4615c6b6b0 | |
Aurora Lahtela | 47d74eee8c | |
甜力怕 | bda96726f8 | |
Aurora Lahtela | 2b93919b5e | |
Aurora Lahtela | a8decff8e8 | |
Aurora Lahtela | 67c487b820 | |
Aurora Lahtela | ff7e7791f3 | |
Aurora Lahtela | 3ad5d577d4 | |
Aurora Lahtela | 7494902e46 | |
Aurora Lahtela | 01ce503c77 | |
AuroraLS3 | 465af8e803 | |
dependabot[bot] | 512defb3f8 | |
dependabot[bot] | 89355d4975 | |
dependabot[bot] | 4a6987710f | |
dependabot[bot] | 79ba13b6fc | |
dependabot[bot] | 7bbc18934a | |
dependabot[bot] | 66844d4c56 | |
dependabot[bot] | d68bf9fca9 | |
dependabot[bot] | 6584ca0cb7 | |
Aurora Lahtela | 8356a0d52e | |
Aurora Lahtela | c8d0cc91b6 | |
Aurora Lahtela | ae85f39871 | |
Aurora Lahtela | 1fdd3289a6 | |
Aurora Lahtela | 9e08794ddd | |
Aurora Lahtela | 34a731b70a | |
Aurora Lahtela | 673cb4cfdb | |
Aurora Lahtela | 8e94d26ff3 | |
dependabot[bot] | f9d2b0767f | |
dependabot[bot] | 7dfb933295 | |
dependabot[bot] | 8e0b2d2734 | |
dependabot[bot] | 3717eacad1 | |
dependabot[bot] | 6f179a133b | |
dependabot[bot] | 0ec5552d90 | |
dependabot[bot] | 2a387bd0dd | |
dependabot[bot] | 26b69e604c | |
dependabot[bot] | 3e41c4dccf | |
dependabot[bot] | ae98bcfd69 | |
dependabot[bot] | 05f07da915 | |
AuroraLS3 | d4ab5a53f8 |
|
@ -23,16 +23,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: 📥 Checkout git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: ☕ Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
- name: 💼 Load Gradle Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
@ -54,12 +54,12 @@ jobs:
|
|||
echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV
|
||||
echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV
|
||||
- name: 📤 Upload Plan.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar
|
||||
- name: 📤 Upload PlanFabric.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar
|
||||
|
|
|
@ -16,8 +16,8 @@ plugins {
|
|||
id 'java-library'
|
||||
id "jacoco"
|
||||
id "checkstyle"
|
||||
id "org.sonarqube" version "4.4.1.3373"
|
||||
id 'fabric-loom' version '1.3-SNAPSHOT' apply false
|
||||
id "org.sonarqube" version "5.0.0.4638"
|
||||
id 'fabric-loom' version '1.6-SNAPSHOT' apply false
|
||||
}
|
||||
|
||||
apply plugin: 'nebula-aggregate-javadocs'
|
||||
|
@ -69,9 +69,9 @@ subprojects {
|
|||
}
|
||||
|
||||
ext {
|
||||
daggerVersion = "2.50"
|
||||
daggerVersion = "2.51.1"
|
||||
|
||||
palVersion = "5.1.0"
|
||||
palVersion = "5.2.0"
|
||||
|
||||
bukkitVersion = "1.13.2-R0.1-SNAPSHOT"
|
||||
spigotVersion = "1.13.2-R0.1-SNAPSHOT"
|
||||
|
@ -83,32 +83,33 @@ subprojects {
|
|||
redisBungeeVersion = "0.3.8-SNAPSHOT"
|
||||
redisBungeeProxioDevVersion = "0.7.3"
|
||||
|
||||
commonsTextVersion = "1.11.0"
|
||||
commonsCompressVersion = "1.25.0"
|
||||
commonsCodecVersion = "1.16.0"
|
||||
commonsTextVersion = "1.12.0"
|
||||
commonsCompressVersion = "1.26.2"
|
||||
commonsCodecVersion = "1.17.0"
|
||||
caffeineVersion = "3.1.8"
|
||||
jettyVersion = "11.0.19"
|
||||
jettyVersion = "11.0.21"
|
||||
caffeineVersion = "2.9.2"
|
||||
mysqlVersion = "8.2.0"
|
||||
mariadbVersion = "3.3.2"
|
||||
mysqlVersion = "8.4.0"
|
||||
mariadbVersion = "3.4.0"
|
||||
sqliteVersion = "3.42.0.1"
|
||||
adventureVersion = "4.14.0"
|
||||
adventureVersion = "4.17.0"
|
||||
hikariVersion = "5.1.0"
|
||||
slf4jVersion = "2.0.10"
|
||||
slf4jVersion = "2.0.13"
|
||||
geoIpVersion = "4.2.0"
|
||||
gsonVersion = "2.10.1"
|
||||
gsonVersion = "2.11.0"
|
||||
dependencyDownloadVersion = "1.3.1"
|
||||
ipAddressMatcherVersion = "5.4.0"
|
||||
ipAddressMatcherVersion = "5.5.0"
|
||||
jasyptVersion = "1.9.3"
|
||||
|
||||
bstatsVersion = "3.0.2"
|
||||
placeholderapiVersion = "2.11.5"
|
||||
nkPlaceholderapiVersion = "1.4-SNAPSHOT"
|
||||
|
||||
junitVersion = "5.10.1"
|
||||
mockitoVersion = "5.8.0"
|
||||
testContainersVersion = "1.19.3"
|
||||
swaggerVersion = "2.2.20"
|
||||
junitVersion = "5.10.2"
|
||||
mockitoVersion = "5.12.0"
|
||||
seleniumVersion = "4.21.0"
|
||||
testContainersVersion = "1.19.8"
|
||||
swaggerVersion = "2.2.22"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.bukkit.command.PluginCommand;
|
|||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
@ -62,7 +63,18 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
|||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
abstractionLayer = new BukkitPlatformLayer(this);
|
||||
if (isFolia()) {
|
||||
try {
|
||||
// Attempt to load and use the Folia library for Java 17+
|
||||
Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer");
|
||||
abstractionLayer = (PlatformAbstractionLayer) foliaPlatformLayer.getConstructor(JavaPlugin.class).newInstance(this);
|
||||
} catch (Exception e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to load FoliaPlatformLayer", e);
|
||||
abstractionLayer = new BukkitPlatformLayer(this);
|
||||
}
|
||||
} else {
|
||||
abstractionLayer = new BukkitPlatformLayer(this);
|
||||
}
|
||||
pluginLogger = abstractionLayer.getPluginLogger();
|
||||
runnableFactory = abstractionLayer.getRunnableFactory();
|
||||
}
|
||||
|
@ -167,7 +179,18 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
|||
|
||||
public void cancelAllTasks() {
|
||||
runnableFactory.cancelAllKnownTasks();
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
if (!isFolia()) {
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFolia() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.gathering.listeners.bukkit;
|
||||
|
||||
import com.djrapitops.plan.gathering.JoinAddressValidator;
|
||||
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
|
||||
import com.djrapitops.plan.gathering.domain.BukkitPlayerData;
|
||||
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
|
||||
|
@ -28,6 +29,8 @@ import com.djrapitops.plan.identification.ServerUUID;
|
|||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import org.bukkit.event.EventHandler;
|
||||
|
@ -50,6 +53,7 @@ public class PlayerOnlineListener implements Listener {
|
|||
|
||||
private final PlayerJoinEventConsumer playerJoinEventConsumer;
|
||||
private final PlayerLeaveEventConsumer playerLeaveEventConsumer;
|
||||
private final JoinAddressValidator joinAddressValidator;
|
||||
private final JoinAddressCache joinAddressCache;
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
|
@ -61,6 +65,7 @@ public class PlayerOnlineListener implements Listener {
|
|||
public PlayerOnlineListener(
|
||||
PlayerJoinEventConsumer playerJoinEventConsumer,
|
||||
PlayerLeaveEventConsumer playerLeaveEventConsumer,
|
||||
JoinAddressValidator joinAddressValidator,
|
||||
JoinAddressCache joinAddressCache,
|
||||
ServerInfo serverInfo,
|
||||
DBSystem dbSystem,
|
||||
|
@ -69,6 +74,7 @@ public class PlayerOnlineListener implements Listener {
|
|||
) {
|
||||
this.playerJoinEventConsumer = playerJoinEventConsumer;
|
||||
this.playerLeaveEventConsumer = playerLeaveEventConsumer;
|
||||
this.joinAddressValidator = joinAddressValidator;
|
||||
this.joinAddressCache = joinAddressCache;
|
||||
this.serverInfo = serverInfo;
|
||||
this.dbSystem = dbSystem;
|
||||
|
@ -82,18 +88,14 @@ public class PlayerOnlineListener implements Listener {
|
|||
UUID playerUUID = event.getPlayer().getUniqueId();
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult();
|
||||
boolean notWhitelisted = PlayerLoginEvent.Result.KICK_WHITELIST == event.getResult();
|
||||
|
||||
String address = event.getHostname();
|
||||
if (!address.isEmpty()) {
|
||||
if (address.contains(":")) {
|
||||
address = address.substring(0, address.lastIndexOf(':'));
|
||||
}
|
||||
if (address.contains("\u0000")) {
|
||||
address = address.substring(0, address.indexOf('\u0000'));
|
||||
}
|
||||
if (address.contains("fml")) {
|
||||
address = address.substring(0, address.lastIndexOf("fml"));
|
||||
}
|
||||
if (notWhitelisted) {
|
||||
dbSystem.getDatabase().executeTransaction(new StoreAllowlistBounceTransaction(playerUUID, event.getPlayer().getName(), serverUUID, System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
@Untrusted String address = joinAddressValidator.sanitize(event.getHostname());
|
||||
if (joinAddressValidator.isValid(address)) {
|
||||
joinAddressCache.put(playerUUID, address);
|
||||
}
|
||||
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned));
|
||||
|
|
|
@ -89,9 +89,9 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
|||
startRecording = new ConcurrentHashMap<>();
|
||||
playerHistory = new HashMap<>();
|
||||
|
||||
Optional<PingMethod> pingMethod = loadPingMethod();
|
||||
if (pingMethod.isPresent()) {
|
||||
this.pingMethod = pingMethod.get();
|
||||
Optional<PingMethod> loaded = loadPingMethod();
|
||||
if (loaded.isPresent()) {
|
||||
this.pingMethod = loaded.get();
|
||||
pingMethodAvailable = true;
|
||||
} else {
|
||||
pingMethodAvailable = false;
|
||||
|
|
|
@ -3,8 +3,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
|||
|
||||
plugins {
|
||||
id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion"
|
||||
id "com.github.node-gradle.node" version "7.0.1"
|
||||
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.20"
|
||||
id "com.github.node-gradle.node" version "7.0.2"
|
||||
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.22"
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
@ -56,7 +56,7 @@ dependencies {
|
|||
shadow project(":extensions")
|
||||
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
|
||||
compileOnly "net.kyori:adventure-api:4.14.0"
|
||||
compileOnly "net.kyori:adventure-api:$adventureVersion"
|
||||
shadow("dev.vankka:dependencydownload-runtime:$dependencyDownloadVersion") {
|
||||
// Effectively disables relocating
|
||||
exclude module: "jar-relocator"
|
||||
|
@ -64,6 +64,7 @@ dependencies {
|
|||
mysqlDriver "com.mysql:mysql-connector-j:$mysqlVersion"
|
||||
mariadbDriver "org.mariadb.jdbc:mariadb-java-client:$mariadbVersion"
|
||||
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||
sqliteDriver "org.slf4j:slf4j-nop:1.7.36"
|
||||
ipAddressMatcher "com.github.seancfoley:ipaddress:$ipAddressMatcherVersion"
|
||||
|
||||
shadow "org.apache.commons:commons-text:$commonsTextVersion"
|
||||
|
@ -86,7 +87,7 @@ dependencies {
|
|||
|
||||
|
||||
// Swagger annotations
|
||||
implementation "jakarta.ws.rs:jakarta.ws.rs-api:3.1.0"
|
||||
implementation "jakarta.ws.rs:jakarta.ws.rs-api:4.0.0"
|
||||
implementation "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion"
|
||||
implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:$swaggerVersion"
|
||||
|
||||
|
@ -94,12 +95,21 @@ dependencies {
|
|||
testArtifacts project(":extensions:adventure")
|
||||
testImplementation project(":extensions:adventure")
|
||||
testImplementation "com.google.code.gson:gson:$gsonVersion"
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:4.12.1"
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:$seleniumVersion"
|
||||
testImplementation "org.testcontainers:testcontainers:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:nginx:$testContainersVersion"
|
||||
}
|
||||
|
||||
test {
|
||||
environment "PLAN_TEST_NODE_STRING", "String"
|
||||
environment "PLAN_TEST_NODE_BOOLEAN", "true"
|
||||
environment "PLAN_TEST_NODE_INTEGER", "5"
|
||||
environment "PLAN_TEST_NODE_DOUBLE", "0.5"
|
||||
environment "PLAN_TEST_NODE_LONG", "9223372036854775807"
|
||||
environment "PLAN_TEST_NODE_STRINGLIST", "- Test\n- Another"
|
||||
}
|
||||
|
||||
task updateVersion(type: Copy) {
|
||||
from('src/main/resources') {
|
||||
include 'plugin.yml'
|
||||
|
@ -232,8 +242,12 @@ artifacts {
|
|||
}
|
||||
|
||||
processResources {
|
||||
dependsOn copyYarnBuildResults
|
||||
dependsOn determineAssetModifications
|
||||
// Skips Yarn build on Jitpack since Jitpack doesn't offer gclib version compatible with Node 20
|
||||
// Jitpack build is used mainly for java dependencies.
|
||||
if (!project.hasProperty("isJitpack")) {
|
||||
dependsOn copyYarnBuildResults
|
||||
dependsOn determineAssetModifications
|
||||
}
|
||||
dependsOn generateResourceForMySQLDriver
|
||||
dependsOn generateResourceForSQLiteDriver
|
||||
dependsOn generateResourceForIpAddressMatcher
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResolverSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResourceSvc;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.ListenerSvc;
|
||||
import com.djrapitops.plan.settings.SchedulerSvc;
|
||||
import com.djrapitops.plan.settings.SettingsSvc;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Breaks up {@link PlanSystem} to be a smaller class.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class ApiServices {
|
||||
|
||||
private final ComponentSvc componentService;
|
||||
private final ResolverSvc resolverService;
|
||||
private final ResourceSvc resourceService;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final QuerySvc queryService;
|
||||
private final ListenerSvc listenerService;
|
||||
private final SettingsSvc settingsService;
|
||||
private final SchedulerSvc schedulerService;
|
||||
|
||||
@Inject
|
||||
public ApiServices(
|
||||
ComponentSvc componentService,
|
||||
ResolverSvc resolverService,
|
||||
ResourceSvc resourceService,
|
||||
ExtensionSvc extensionService,
|
||||
QuerySvc queryService,
|
||||
ListenerSvc listenerService,
|
||||
SettingsSvc settingsService,
|
||||
SchedulerSvc schedulerService
|
||||
) {
|
||||
this.componentService = componentService;
|
||||
this.resolverService = resolverService;
|
||||
this.resourceService = resourceService;
|
||||
this.extensionService = extensionService;
|
||||
this.queryService = queryService;
|
||||
this.listenerService = listenerService;
|
||||
this.settingsService = settingsService;
|
||||
this.schedulerService = schedulerService;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
extensionService.register();
|
||||
componentService.register();
|
||||
resolverService.register();
|
||||
resourceService.register();
|
||||
listenerService.register();
|
||||
settingsService.register();
|
||||
schedulerService.register();
|
||||
queryService.register();
|
||||
}
|
||||
|
||||
public void registerExtensions() {
|
||||
extensionService.registerExtensions();
|
||||
}
|
||||
|
||||
public void disableExtensionDataUpdates() {
|
||||
extensionService.disableUpdates();
|
||||
}
|
||||
|
||||
public ComponentSvc getComponentService() {
|
||||
return componentService;
|
||||
}
|
||||
|
||||
public ResolverSvc getResolverService() {
|
||||
return resolverService;
|
||||
}
|
||||
|
||||
public ResourceSvc getResourceService() {
|
||||
return resourceService;
|
||||
}
|
||||
|
||||
public ExtensionSvc getExtensionService() {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
public QuerySvc getQueryService() {
|
||||
return queryService;
|
||||
}
|
||||
|
||||
public ListenerSvc getListenerService() {
|
||||
return listenerService;
|
||||
}
|
||||
|
||||
public SettingsSvc getSettingsService() {
|
||||
return settingsService;
|
||||
}
|
||||
|
||||
public SchedulerSvc getSchedulerService() {
|
||||
return schedulerService;
|
||||
}
|
||||
}
|
|
@ -17,25 +17,17 @@
|
|||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.api.PlanAPI;
|
||||
import com.djrapitops.plan.component.ComponentSvc;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.delivery.export.ExportSystem;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.web.ResolverSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResourceSvc;
|
||||
import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker;
|
||||
import com.djrapitops.plan.delivery.webserver.WebServerSystem;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.gathering.cache.CacheSystem;
|
||||
import com.djrapitops.plan.gathering.importing.ImportSystem;
|
||||
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.ConfigSystem;
|
||||
import com.djrapitops.plan.settings.ListenerSvc;
|
||||
import com.djrapitops.plan.settings.SchedulerSvc;
|
||||
import com.djrapitops.plan.settings.SettingsSvc;
|
||||
import com.djrapitops.plan.settings.locale.LocaleSystem;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
|
@ -77,14 +69,7 @@ public class PlanSystem implements SubSystem {
|
|||
private final ImportSystem importSystem;
|
||||
private final ExportSystem exportSystem;
|
||||
private final DeliveryUtilities deliveryUtilities;
|
||||
private final ComponentSvc componentService;
|
||||
private final ResolverSvc resolverService;
|
||||
private final ResourceSvc resourceService;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final QuerySvc queryService;
|
||||
private final ListenerSvc listenerService;
|
||||
private final SettingsSvc settingsService;
|
||||
private final SchedulerSvc schedulerService;
|
||||
private final ApiServices apiServices;
|
||||
private final PluginLogger logger;
|
||||
private final ErrorLogger errorLogger;
|
||||
|
||||
|
@ -104,16 +89,9 @@ public class PlanSystem implements SubSystem {
|
|||
ImportSystem importSystem,
|
||||
ExportSystem exportSystem,
|
||||
DeliveryUtilities deliveryUtilities,
|
||||
ComponentSvc componentService,
|
||||
ResolverSvc resolverService,
|
||||
ResourceSvc resourceService,
|
||||
ExtensionSvc extensionService,
|
||||
QuerySvc queryService,
|
||||
ListenerSvc listenerService,
|
||||
SettingsSvc settingsService,
|
||||
SchedulerSvc schedulerService,
|
||||
PluginLogger logger,
|
||||
ErrorLogger errorLogger,
|
||||
ApiServices apiServices, // API v5
|
||||
@SuppressWarnings("deprecation") PlanAPI.PlanAPIHolder apiHolder // Deprecated PlanAPI, backwards compatibility
|
||||
) {
|
||||
this.files = files;
|
||||
|
@ -130,16 +108,9 @@ public class PlanSystem implements SubSystem {
|
|||
this.importSystem = importSystem;
|
||||
this.exportSystem = exportSystem;
|
||||
this.deliveryUtilities = deliveryUtilities;
|
||||
this.componentService = componentService;
|
||||
this.resolverService = resolverService;
|
||||
this.resourceService = resourceService;
|
||||
this.extensionService = extensionService;
|
||||
this.queryService = queryService;
|
||||
this.listenerService = listenerService;
|
||||
this.settingsService = settingsService;
|
||||
this.schedulerService = schedulerService;
|
||||
this.logger = logger;
|
||||
this.errorLogger = errorLogger;
|
||||
this.apiServices = apiServices;
|
||||
|
||||
logger.info("§2");
|
||||
logger.info("§2 ██▌");
|
||||
|
@ -162,14 +133,7 @@ public class PlanSystem implements SubSystem {
|
|||
* Enables the rest of the systems that are not enabled in {@link #enableForCommands()}.
|
||||
*/
|
||||
public void enableOtherThanCommands() {
|
||||
extensionService.register();
|
||||
componentService.register();
|
||||
resolverService.register();
|
||||
resourceService.register();
|
||||
listenerService.register();
|
||||
settingsService.register();
|
||||
schedulerService.register();
|
||||
queryService.register();
|
||||
apiServices.register();
|
||||
|
||||
enableSystems(
|
||||
processing,
|
||||
|
@ -193,7 +157,7 @@ public class PlanSystem implements SubSystem {
|
|||
));
|
||||
}
|
||||
|
||||
extensionService.registerExtensions();
|
||||
apiServices.registerExtensions();
|
||||
enabled = true;
|
||||
|
||||
String javaVersion = System.getProperty("java.specification.version");
|
||||
|
@ -223,7 +187,7 @@ public class PlanSystem implements SubSystem {
|
|||
enabled = false;
|
||||
Formatters.clearSingleton();
|
||||
|
||||
extensionService.disableUpdates();
|
||||
apiServices.disableExtensionDataUpdates();
|
||||
|
||||
disableSystems(
|
||||
taskSystem,
|
||||
|
@ -316,12 +280,8 @@ public class PlanSystem implements SubSystem {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
public ExtensionSvc getExtensionService() {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
public ComponentSvc getComponentService() {
|
||||
return componentService;
|
||||
public ApiServices getApiServices() {
|
||||
return apiServices;
|
||||
}
|
||||
|
||||
public static long getServerEnableTime() {
|
||||
|
|
|
@ -140,13 +140,21 @@ public class PlanCommand {
|
|||
}
|
||||
|
||||
public List<String> serverNames(CMDSender sender, @Untrusted Arguments arguments) {
|
||||
@Untrusted String asString = arguments.concatenate(" ");
|
||||
return tabCompleteCache.getMatchingServerIdentifiers(asString);
|
||||
if (sender.hasPermission(Permissions.SERVER)) {
|
||||
@Untrusted String asString = arguments.concatenate(" ");
|
||||
return tabCompleteCache.getMatchingServerIdentifiers(asString);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private List<String> playerNames(CMDSender sender, @Untrusted Arguments arguments) {
|
||||
@Untrusted String asString = arguments.concatenate(" ");
|
||||
return tabCompleteCache.getMatchingPlayerIdentifiers(asString);
|
||||
if (sender.hasPermission(Permissions.PLAYER_OTHER)) {
|
||||
@Untrusted String asString = arguments.concatenate(" ");
|
||||
return tabCompleteCache.getMatchingPlayerIdentifiers(asString);
|
||||
} else if (sender.hasPermission(Permissions.PLAYER_SELF)) {
|
||||
return sender.getPlayerName().map(List::of).orElse(List.of());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private Subcommand serverCommand() {
|
||||
|
@ -403,6 +411,9 @@ public class PlanCommand {
|
|||
}
|
||||
|
||||
private List<String> getBackupFilenames(CMDSender sender, @Untrusted Arguments arguments) {
|
||||
if (!sender.hasPermission(Permissions.DATA_RESTORE)) {
|
||||
return List.of();
|
||||
}
|
||||
if (arguments.get(1).isPresent()) {
|
||||
return DBType.names();
|
||||
}
|
||||
|
@ -531,6 +542,9 @@ public class PlanCommand {
|
|||
}
|
||||
|
||||
private List<String> webGroupTabComplete(CMDSender sender, @Untrusted Arguments arguments) {
|
||||
if (!sender.hasPermission(Permissions.SET_GROUP)) {
|
||||
return List.of();
|
||||
}
|
||||
Optional<String> groupArgument = arguments.get(1);
|
||||
if (groupArgument.isPresent()) {
|
||||
return tabCompleteCache.getMatchingWebGroupNames(groupArgument.get());
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.djrapitops.plan.delivery.formatting.Formatter;
|
|||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.exceptions.ExportException;
|
||||
import com.djrapitops.plan.gathering.domain.GeoInfo;
|
||||
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
||||
import com.djrapitops.plan.gathering.importing.ImportSystem;
|
||||
import com.djrapitops.plan.gathering.importing.importers.Importer;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
|
@ -42,6 +43,7 @@ import com.djrapitops.plan.settings.locale.Locale;
|
|||
import com.djrapitops.plan.settings.locale.lang.CommandLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.GenericLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.HelpLang;
|
||||
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||
|
@ -258,12 +260,17 @@ public class DataUtilityCommands {
|
|||
Optional<GeoInfo> mostRecentGeoInfo = new GeoInfoMutator(geoInfo).mostRecent();
|
||||
String geolocation = mostRecentGeoInfo.isPresent() ? mostRecentGeoInfo.get().getGeolocation() : "-";
|
||||
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
|
||||
String latestJoinAddress = sessionsMutator.latestSession()
|
||||
.flatMap(session -> session.getExtraData(JoinAddress.class))
|
||||
.map(JoinAddress::getAddress)
|
||||
.orElse("-");
|
||||
|
||||
String table = locale.getString(CommandLang.HEADER_INSPECT, playerName) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_ACTIVITY_INDEX, activityIndex.getFormattedValue(formatters.decimals()), activityIndex.getGroup()) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_REGISTERED, timestamp.apply(() -> registered)) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_LAST_SEEN, timestamp.apply(() -> lastSeen)) + '\n' +
|
||||
locale.getString(CommandLang.INGAME_GEOLOCATION, geolocation) + '\n' +
|
||||
" §2" + locale.getString(HtmlLang.LABEL_LABEL_JOIN_ADDRESS) + ": §f" + latestJoinAddress + '\n' +
|
||||
locale.getString(CommandLang.INGAME_TIMES_KICKED, player.getValue(PlayerKeys.KICK_COUNT).orElse(0)) + '\n' +
|
||||
'\n' +
|
||||
locale.getString(CommandLang.INGAME_PLAYTIME, length.apply(sessionsMutator.toPlaytime())) + '\n' +
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Object that has a value tied to a date.
|
||||
*
|
||||
|
@ -39,4 +41,25 @@ public class DateObj<T> implements DateHolder {
|
|||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DateObj<?> dateObj = (DateObj<?>) o;
|
||||
return getDate() == dateObj.getDate() && Objects.equals(getValue(), dateObj.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getDate(), getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DateObj{" +
|
||||
"date=" + date +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ import java.util.Objects;
|
|||
*/
|
||||
public class JoinAddressCount implements Comparable<JoinAddressCount> {
|
||||
|
||||
private final int count;
|
||||
private int count;
|
||||
private String joinAddress;
|
||||
|
||||
public JoinAddressCount(Map.Entry<String, Integer> entry) {
|
||||
|
@ -52,6 +52,10 @@ public class JoinAddressCount implements Comparable<JoinAddressCount> {
|
|||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JoinAddressCount other) {
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(this.joinAddress, other.joinAddress);
|
||||
|
|
|
@ -19,6 +19,9 @@ package com.djrapitops.plan.delivery.domain.auth;
|
|||
import com.djrapitops.plan.settings.locale.lang.Lang;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
|
@ -47,7 +50,8 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
|
||||
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
|
||||
@Deprecated
|
||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
|
||||
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
||||
PAGE_NETWORK_RETENTION("See Player Retention -tab"),
|
||||
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
|
||||
|
@ -82,7 +86,8 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
|
||||
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
|
||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
|
||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
|
||||
@Deprecated
|
||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
|
||||
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
|
||||
PAGE_SERVER_RETENTION("See Player Retention -tab"),
|
||||
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
|
||||
|
@ -93,6 +98,7 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
|
||||
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
|
||||
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),
|
||||
PAGE_SERVER_ALLOWLIST_BOUNCE("See list of Game allowlist bounces"),
|
||||
|
||||
PAGE_PLAYER("See all of player page"),
|
||||
PAGE_PLAYER_OVERVIEW("See Player Overview -tab"),
|
||||
|
@ -155,4 +161,23 @@ public enum WebPermission implements Supplier<String>, Lang {
|
|||
public String getDefault() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public static WebPermission[] nonDeprecatedValues() {
|
||||
return Arrays.stream(values())
|
||||
.filter(Predicate.not(WebPermission::isDeprecated))
|
||||
.toArray(WebPermission[]::new);
|
||||
}
|
||||
|
||||
public static Optional<WebPermission> findByPermission(String permission) {
|
||||
String name = StringUtils.upperCase(permission).replace('.', '_');
|
||||
try {
|
||||
return Optional.of(valueOf(name));
|
||||
} catch (IllegalArgumentException noSuchEnum) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDeprecated(String permission) {
|
||||
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.domain.datatransfer;
|
||||
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents an event where player bounced off the whitelist.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class AllowlistBounce {
|
||||
|
||||
private final UUID playerUUID;
|
||||
@Untrusted
|
||||
private final String playerName;
|
||||
private final int count;
|
||||
private final long lastTime;
|
||||
|
||||
public AllowlistBounce(UUID playerUUID, String playerName, int count, long lastTime) {
|
||||
this.playerUUID = playerUUID;
|
||||
this.playerName = playerName;
|
||||
this.count = count;
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
public UUID getPlayerUUID() {
|
||||
return playerUUID;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public long getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AllowlistBounce bounce = (AllowlistBounce) o;
|
||||
return getCount() == bounce.getCount() && getLastTime() == bounce.getLastTime() && Objects.equals(getPlayerUUID(), bounce.getPlayerUUID()) && Objects.equals(getPlayerName(), bounce.getPlayerName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getPlayerUUID(), getPlayerName(), getCount(), getLastTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AllowlistBounce{" +
|
||||
"playerUUID=" + playerUUID +
|
||||
", playerName='" + playerName + '\'' +
|
||||
", count=" + count +
|
||||
", lastTime=" + lastTime +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.domain.datatransfer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents data returned by {@link com.djrapitops.plan.delivery.webserver.resolver.json.PlayerJoinAddressJSONResolver}.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlayerJoinAddresses {
|
||||
|
||||
private final List<String> joinAddresses;
|
||||
private final Map<UUID, String> joinAddressByPlayer;
|
||||
|
||||
public PlayerJoinAddresses(List<String> joinAddresses, Map<UUID, String> joinAddressByPlayer) {
|
||||
this.joinAddresses = joinAddresses;
|
||||
this.joinAddressByPlayer = joinAddressByPlayer;
|
||||
}
|
||||
|
||||
public List<String> getJoinAddresses() {
|
||||
return joinAddresses;
|
||||
}
|
||||
|
||||
public Map<UUID, String> getJoinAddressByPlayer() {
|
||||
return joinAddressByPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PlayerJoinAddresses that = (PlayerJoinAddresses) o;
|
||||
return Objects.equals(getJoinAddresses(), that.getJoinAddresses()) && Objects.equals(getJoinAddressByPlayer(), that.getJoinAddressByPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getJoinAddresses(), getJoinAddressByPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerJoinAddresses{" +
|
||||
"joinAddresses=" + joinAddresses +
|
||||
", joinAddressByPlayer=" + joinAddressByPlayer +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -28,15 +28,15 @@ import java.util.Objects;
|
|||
*/
|
||||
public class ServerSpecificLineGraph {
|
||||
|
||||
private final List<Double[]> points;
|
||||
private final List<Number[]> points;
|
||||
private final ServerDto server;
|
||||
|
||||
public ServerSpecificLineGraph(List<Double[]> points, ServerDto server) {
|
||||
public ServerSpecificLineGraph(List<Number[]> points, ServerDto server) {
|
||||
this.points = points;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public List<Double[]> getPoints() {
|
||||
public List<Number[]> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
|
|
|
@ -166,6 +166,12 @@ public class Exporter extends FileExporter {
|
|||
}
|
||||
|
||||
public void exportReact() throws ExportException {
|
||||
if (config.isFalse(ExportSettings.PLAYER_PAGES)
|
||||
&& config.isFalse(ExportSettings.SERVER_PAGE)
|
||||
&& config.isFalse(ExportSettings.PLAYERS_PAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path toDirectory = config.getPageExportPath();
|
||||
|
||||
try {
|
||||
|
|
|
@ -121,7 +121,6 @@ public class NetworkPageExporter extends FileExporter {
|
|||
"graph?type=uniqueAndNew",
|
||||
"graph?type=hourlyUniqueAndNew",
|
||||
"graph?type=serverPie",
|
||||
"graph?type=joinAddressPie",
|
||||
"graph?type=joinAddressByDay",
|
||||
"graph?type=activity",
|
||||
"graph?type=geolocation",
|
||||
|
|
|
@ -100,6 +100,7 @@ public class ServerPageExporter extends FileExporter {
|
|||
server + serverUUID + "/playerbase",
|
||||
server + serverUUID + "/join-addresses",
|
||||
server + serverUUID + "/retention",
|
||||
server + serverUUID + "/allowlist",
|
||||
server + serverUUID + "/players",
|
||||
server + serverUUID + "/geolocations",
|
||||
server + serverUUID + "/performance",
|
||||
|
@ -137,7 +138,6 @@ public class ServerPageExporter extends FileExporter {
|
|||
"graph?type=geolocation&server=" + serverUUID,
|
||||
"graph?type=uniqueAndNew&server=" + serverUUID,
|
||||
"graph?type=hourlyUniqueAndNew&server=" + serverUUID,
|
||||
"graph?type=joinAddressPie&server=" + serverUUID,
|
||||
"graph?type=joinAddressByDay&server=" + serverUUID,
|
||||
"graph?type=serverCalendar&server=" + serverUUID,
|
||||
"graph?type=punchCard&server=" + serverUUID,
|
||||
|
@ -148,7 +148,8 @@ public class ServerPageExporter extends FileExporter {
|
|||
"extensionData?server=" + serverUUID,
|
||||
"serverIdentity?server=" + serverUUID,
|
||||
"retention?server=" + serverUUID,
|
||||
"joinAddresses?server=" + serverUUID
|
||||
"joinAddresses?server=" + serverUUID,
|
||||
"gameAllowlistBounces?server=" + serverUUID
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,11 @@ public class Contributors {
|
|||
new Contributor("xlanyleeet", LANG),
|
||||
new Contributor("Jumala9163", LANG),
|
||||
new Contributor("Dreeam-qwq", CODE),
|
||||
new Contributor("jhqwqmc", LANG)
|
||||
new Contributor("jhqwqmc", LANG),
|
||||
new Contributor("liuzhen932", LANG),
|
||||
new Contributor("Sniper_TVmc", LANG),
|
||||
new Contributor("mcmdev", CODE),
|
||||
new Contributor("ZhangYuheng", CODE)
|
||||
};
|
||||
|
||||
private Contributors() {
|
||||
|
|
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.rendering.json;
|
|||
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.delivery.domain.RetentionData;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
|
||||
|
@ -34,6 +35,7 @@ import com.djrapitops.plan.identification.Server;
|
|||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
|
||||
import com.djrapitops.plan.settings.config.paths.TimeSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
|
@ -48,6 +50,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.PlayerRetentionQuer
|
|||
import com.djrapitops.plan.storage.database.queries.objects.*;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.playertable.NetworkTablePlayersQuery;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.playertable.ServerTablePlayersQuery;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
||||
import com.djrapitops.plan.utilities.comparators.SessionStartComparator;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
|
@ -160,14 +163,52 @@ public class JSONFactory {
|
|||
return db.query(PlayerRetentionQueries.fetchRetentionData());
|
||||
}
|
||||
|
||||
public Map<UUID, String> playerJoinAddresses(ServerUUID serverUUID) {
|
||||
Database db = dbSystem.getDatabase();
|
||||
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
|
||||
private static void removeFiltered(Map<UUID, String> addressByPlayerUUID, List<String> filteredJoinAddresses) {
|
||||
if (filteredJoinAddresses.isEmpty() || filteredJoinAddresses.equals(List.of("play.example.com"))) return;
|
||||
|
||||
Set<UUID> toRemove = new HashSet<>();
|
||||
// Remove filtered addresses from the data
|
||||
for (Map.Entry<UUID, String> entry : addressByPlayerUUID.entrySet()) {
|
||||
if (filteredJoinAddresses.contains(entry.getValue())) {
|
||||
toRemove.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (UUID playerUUID : toRemove) {
|
||||
addressByPlayerUUID.put(playerUUID, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<UUID, String> playerJoinAddresses() {
|
||||
public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
|
||||
Database db = dbSystem.getDatabase();
|
||||
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
|
||||
List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
|
||||
if (includeByPlayerMap) {
|
||||
Map<UUID, String> addresses = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
|
||||
|
||||
removeFiltered(addresses, filteredJoinAddresses);
|
||||
|
||||
return new PlayerJoinAddresses(
|
||||
addresses.values().stream().distinct().sorted().collect(Collectors.toList()),
|
||||
addresses
|
||||
);
|
||||
} else {
|
||||
List<String> addresses = db.query(JoinAddressQueries.uniqueJoinAddresses(serverUUID));
|
||||
addresses.removeAll(filteredJoinAddresses);
|
||||
return new PlayerJoinAddresses(addresses, null);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerJoinAddresses playerJoinAddresses(boolean includeByPlayerMap) {
|
||||
Database db = dbSystem.getDatabase();
|
||||
List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
|
||||
List<String> unique = db.query(JoinAddressQueries.uniqueJoinAddresses());
|
||||
unique.removeAll(filteredJoinAddresses);
|
||||
if (includeByPlayerMap) {
|
||||
Map<UUID, String> latest = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
|
||||
removeFiltered(latest, filteredJoinAddresses);
|
||||
return new PlayerJoinAddresses(unique, latest);
|
||||
} else {
|
||||
return new PlayerJoinAddresses(unique, null);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) {
|
||||
|
@ -320,9 +361,9 @@ public class JSONFactory {
|
|||
|
||||
tableEntries.add(Maps.builder(String.class, Object.class)
|
||||
.put("country", geolocation)
|
||||
.put("avg_ping", formatters.decimals().apply(ping.getAverage()) + " ms")
|
||||
.put("min_ping", ping.getMin() + " ms")
|
||||
.put("max_ping", ping.getMax() + " ms")
|
||||
.put("avg_ping", ping.getAverage())
|
||||
.put("min_ping", ping.getMin())
|
||||
.put("max_ping", ping.getMax())
|
||||
.build());
|
||||
}
|
||||
return tableEntries;
|
||||
|
|
|
@ -154,9 +154,9 @@ public class PlayerJSONCreator {
|
|||
private Map<String, Object> createPingGraphJson(PlayerContainer player) {
|
||||
PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING));
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("min_ping_series", pingGraph.getMinGraph().getPoints())
|
||||
.put("avg_ping_series", pingGraph.getAvgGraph().getPoints())
|
||||
.put("max_ping_series", pingGraph.getMaxGraph().getPoints())
|
||||
.put("min_ping_series", pingGraph.getMinGraph().getPointArrays())
|
||||
.put("avg_ping_series", pingGraph.getAvgGraph().getPointArrays())
|
||||
.put("max_ping_series", pingGraph.getMaxGraph().getPointArrays())
|
||||
.put("colors", Maps.builder(String.class, String.class)
|
||||
.put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING))
|
||||
.put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING))
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateHolder;
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
|
@ -55,17 +54,14 @@ import java.util.concurrent.TimeUnit;
|
|||
@Singleton
|
||||
public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<String, Object>> {
|
||||
|
||||
private final Formatter<Long> day;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final ServerSensor<?> serverSensor;
|
||||
|
||||
private final Formatter<Long> timeAmount;
|
||||
private final Formatter<Double> decimals;
|
||||
private final Formatter<Double> percentage;
|
||||
private final ServerUptimeCalculator serverUptimeCalculator;
|
||||
private final Formatter<DateHolder> year;
|
||||
|
||||
@Inject
|
||||
public ServerOverviewJSONCreator(
|
||||
|
@ -82,9 +78,6 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
this.serverSensor = serverSensor;
|
||||
this.serverUptimeCalculator = serverUptimeCalculator;
|
||||
|
||||
year = formatters.year();
|
||||
day = formatters.dayLong();
|
||||
timeAmount = formatters.timeAmount();
|
||||
decimals = formatters.decimals();
|
||||
percentage = formatters.percentage();
|
||||
}
|
||||
|
@ -118,7 +111,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
double averageTPS = tpsMutator.averageTPS();
|
||||
sevenDays.put("average_tps", averageTPS != -1 ? decimals.apply(averageTPS) : GenericLang.UNAVAILABLE.getKey());
|
||||
sevenDays.put("low_tps_spikes", tpsMutator.lowTpsSpikeCount(config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED)));
|
||||
sevenDays.put("downtime", timeAmount.apply(tpsMutator.serverDownTime()));
|
||||
sevenDays.put("downtime", tpsMutator.serverDownTime());
|
||||
|
||||
return sevenDays;
|
||||
}
|
||||
|
@ -137,18 +130,19 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
numbers.put("online_players", getOnlinePlayers(serverUUID, db));
|
||||
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
|
||||
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
|
||||
numbers.put("last_peak_date", lastPeak.map(year).orElse("-"));
|
||||
numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now, serverUUID));
|
||||
numbers.put("playtime", timeAmount.apply(totalPlaytime));
|
||||
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-");
|
||||
numbers.put("playtime", totalPlaytime);
|
||||
numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
|
||||
numbers.put("sessions", db.query(SessionQueries.sessionCount(0L, now, serverUUID)));
|
||||
numbers.put("player_kills", db.query(KillQueries.playerKillCount(0L, now, serverUUID)));
|
||||
numbers.put("mob_kills", db.query(KillQueries.mobKillCount(0L, now, serverUUID)));
|
||||
numbers.put("deaths", db.query(KillQueries.deathCount(0L, now, serverUUID)));
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount)
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
|
||||
.map(Object.class::cast)
|
||||
.orElse(GenericLang.UNAVAILABLE.getKey()));
|
||||
|
||||
return numbers;
|
||||
|
@ -171,9 +165,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
|
||||
Map<String, Object> weeks = new HashMap<>();
|
||||
|
||||
weeks.put("start", day.apply(twoWeeksAgo));
|
||||
weeks.put("midpoint", day.apply(oneWeekAgo));
|
||||
weeks.put("end", day.apply(now));
|
||||
weeks.put("start", twoWeeksAgo);
|
||||
weeks.put("midpoint", oneWeekAgo);
|
||||
weeks.put("end", now);
|
||||
|
||||
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo, serverUUID));
|
||||
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now, serverUUID));
|
||||
|
@ -199,9 +193,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
|
|||
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now, serverUUID));
|
||||
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
|
||||
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount);
|
||||
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore));
|
||||
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter));
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
|
||||
weeks.put("average_playtime_before", avgPlaytimeBefore);
|
||||
weeks.put("average_playtime_after", avgPlaytimeAfter);
|
||||
weeks.put("average_playtime_trend", avgPlaytimeTrend);
|
||||
|
||||
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo, serverUUID));
|
||||
|
|
|
@ -32,7 +32,6 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraphFactory;
|
|||
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.PieSlice;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
|
||||
|
@ -57,7 +56,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
|
|||
import com.djrapitops.plan.storage.database.queries.objects.*;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
||||
import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator;
|
||||
import com.djrapitops.plan.utilities.comparators.PieSliceComparator;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Lists;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import net.playeranalytics.plugin.scheduling.TimeAmount;
|
||||
|
@ -457,34 +456,6 @@ public class GraphJSONCreator {
|
|||
.build();
|
||||
}
|
||||
|
||||
public Map<String, Object> playerHostnamePieJSONAsMap() {
|
||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses());
|
||||
|
||||
translateUnknown(joinAddresses);
|
||||
|
||||
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
|
||||
slices.sort(new PieSliceComparator());
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("colors", pieColors)
|
||||
.put("slices", slices)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Map<String, Object> playerHostnamePieJSONAsMap(ServerUUID serverUUID) {
|
||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID));
|
||||
|
||||
translateUnknown(joinAddresses);
|
||||
|
||||
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
|
||||
slices.sort(new PieSliceComparator());
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("colors", pieColors)
|
||||
.put("slices", slices)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void translateUnknown(Map<String, Integer> joinAddresses) {
|
||||
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
if (unknown != null) {
|
||||
|
@ -493,33 +464,65 @@ public class GraphJSONCreator {
|
|||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before) {
|
||||
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before, @Untrusted List<String> addressFilter) {
|
||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
||||
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));
|
||||
|
||||
return mapToJson(pieColors, joinAddresses);
|
||||
}
|
||||
|
||||
public Map<String, Object> joinAddressesByDay(long after, long before) {
|
||||
public Map<String, Object> joinAddressesByDay(long after, long before, @Untrusted List<String> addressFilter) {
|
||||
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
||||
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
|
||||
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));
|
||||
|
||||
return mapToJson(pieColors, joinAddresses);
|
||||
}
|
||||
|
||||
private static void removeFilteredAddresses(List<JoinAddressCount> addresses, List<String> filteredJoinAddresses) {
|
||||
if (filteredJoinAddresses.isEmpty() || filteredJoinAddresses.equals(List.of("play.example.com"))) return;
|
||||
|
||||
List<JoinAddressCount> addressesToRemove = addresses.stream()
|
||||
.filter(address -> filteredJoinAddresses.contains(address.getJoinAddress()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!addressesToRemove.isEmpty()) {
|
||||
Optional<JoinAddressCount> foundUnknownAddressCount = addresses.stream()
|
||||
.filter(address -> address.getJoinAddress().equals(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP))
|
||||
.findFirst();
|
||||
JoinAddressCount unknownAddressCount;
|
||||
if (foundUnknownAddressCount.isEmpty()) {
|
||||
unknownAddressCount = new JoinAddressCount(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 0);
|
||||
addresses.add(unknownAddressCount);
|
||||
} else {
|
||||
unknownAddressCount = foundUnknownAddressCount.get();
|
||||
}
|
||||
|
||||
for (JoinAddressCount toRemove : addressesToRemove) {
|
||||
unknownAddressCount.setCount(unknownAddressCount.getCount() + toRemove.getCount());
|
||||
addresses.remove(toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> mapToJson(String[] pieColors, List<DateObj<Map<String, Integer>>> joinAddresses) {
|
||||
for (DateObj<Map<String, Integer>> addressesByDate : joinAddresses) {
|
||||
translateUnknown(addressesByDate.getValue());
|
||||
}
|
||||
|
||||
List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
|
||||
|
||||
List<JoinAddressCounts> joinAddressCounts = joinAddresses.stream()
|
||||
.map(addressesOnDay -> new JoinAddressCounts(
|
||||
addressesOnDay.getDate(),
|
||||
addressesOnDay.getValue().entrySet()
|
||||
.stream()
|
||||
.map(JoinAddressCount::new)
|
||||
.sorted()
|
||||
.collect(Collectors.toList())))
|
||||
.map(addressesOnDay -> {
|
||||
List<JoinAddressCount> addresses = addressesOnDay.getValue().entrySet()
|
||||
.stream()
|
||||
.map(JoinAddressCount::new)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
removeFilteredAddresses(addresses, filteredJoinAddresses);
|
||||
|
||||
return new JoinAddressCounts(addressesOnDay.getDate(), addresses);
|
||||
})
|
||||
.sorted(new DateHolderOldestComparator())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
@ -537,7 +540,7 @@ public class GraphJSONCreator {
|
|||
List<ServerSpecificLineGraph> proxyGraphs = new ArrayList<>();
|
||||
for (Server proxy : db.query(ServerQueries.fetchProxyServers())) {
|
||||
ServerUUID proxyUUID = proxy.getUuid();
|
||||
List<Double[]> points = Lists.map(
|
||||
List<Number[]> points = Lists.map(
|
||||
db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, proxyUUID)),
|
||||
point -> Point.fromDateObj(point).toArray()
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.HighChart;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This is a LineGraph for any set of Points, thus it is Abstract.
|
||||
|
@ -80,6 +81,10 @@ public class LineGraph implements HighChart {
|
|||
return points;
|
||||
}
|
||||
|
||||
public List<Number[]> getPointArrays() {
|
||||
return getPoints().stream().map(Point::toArray).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void addMissingPoints(StringBuilder arrayBuilder, Long lastX, long date) {
|
||||
long iterate = lastX + gapStrategy.diffToFirstGapPointMs;
|
||||
while (iterate < date) {
|
||||
|
|
|
@ -75,7 +75,7 @@ public class Point {
|
|||
"y=" + y + '}';
|
||||
}
|
||||
|
||||
public Double[] toArray() {
|
||||
return new Double[]{x, y};
|
||||
public Number[] toArray() {
|
||||
return new Number[]{x, y};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.json.graphs.pie;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JoinAddressPie extends Pie {
|
||||
|
||||
JoinAddressPie(Map<String, Integer> joinAddresses) {
|
||||
super(turnToSlices(joinAddresses));
|
||||
}
|
||||
|
||||
private static List<PieSlice> turnToSlices(Map<String, Integer> joinAddresses) {
|
||||
List<PieSlice> slices = new ArrayList<>();
|
||||
for (Map.Entry<String, Integer> address : joinAddresses.entrySet()) {
|
||||
String joinAddress = address.getKey();
|
||||
Integer total = address.getValue();
|
||||
slices.add(new PieSlice(joinAddress, total));
|
||||
}
|
||||
return slices;
|
||||
}
|
||||
}
|
|
@ -68,10 +68,6 @@ public class PieGraphFactory {
|
|||
return new ServerPreferencePie(playtimeByServerName);
|
||||
}
|
||||
|
||||
public Pie joinAddressPie(Map<String, Integer> joinAddresses) {
|
||||
return new JoinAddressPie(joinAddresses);
|
||||
}
|
||||
|
||||
public WorldPie worldPie(WorldTimes worldTimes) {
|
||||
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
|
||||
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.delivery.rendering.json.network;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.DateHolder;
|
||||
import com.djrapitops.plan.delivery.domain.DateObj;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.json.Trend;
|
||||
import com.djrapitops.plan.gathering.ServerSensor;
|
||||
|
@ -50,14 +48,11 @@ import java.util.concurrent.TimeUnit;
|
|||
@Singleton
|
||||
public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<String, Object>> {
|
||||
|
||||
private final Formatter<Long> day;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final ServerSensor<?> serverSensor;
|
||||
private final Formatter<Long> timeAmount;
|
||||
private final ServerUptimeCalculator serverUptimeCalculator;
|
||||
private final Formatter<DateHolder> year;
|
||||
|
||||
@Inject
|
||||
public NetworkOverviewJSONCreator(
|
||||
|
@ -73,10 +68,6 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
this.serverInfo = serverInfo;
|
||||
this.serverSensor = serverSensor;
|
||||
this.serverUptimeCalculator = serverUptimeCalculator;
|
||||
|
||||
year = formatters.year();
|
||||
day = formatters.dayLong();
|
||||
timeAmount = formatters.timeAmount();
|
||||
}
|
||||
|
||||
public Map<String, Object> createJSONAsMap() {
|
||||
|
@ -122,17 +113,18 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
|
||||
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
|
||||
numbers.put("last_peak_date", lastPeak.map(year).orElse("-"));
|
||||
numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-"));
|
||||
numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
|
||||
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
|
||||
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now));
|
||||
numbers.put("playtime", timeAmount.apply(totalPlaytime));
|
||||
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-");
|
||||
numbers.put("playtime", totalPlaytime);
|
||||
numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
|
||||
Long sessionCount = db.query(SessionQueries.sessionCount(0L, now));
|
||||
numbers.put("sessions", sessionCount);
|
||||
numbers.put("session_length_avg", sessionCount != 0 ? timeAmount.apply(totalPlaytime / sessionCount) : "-");
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount)
|
||||
numbers.put("session_length_avg", sessionCount != 0 ? totalPlaytime / sessionCount : "-");
|
||||
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
|
||||
.map(Object.class::cast)
|
||||
.orElse(GenericLang.UNAVAILABLE.getKey()));
|
||||
|
||||
return numbers;
|
||||
|
@ -147,9 +139,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
|
||||
Map<String, Object> weeks = new HashMap<>();
|
||||
|
||||
weeks.put("start", day.apply(twoWeeksAgo));
|
||||
weeks.put("midpoint", day.apply(oneWeekAgo));
|
||||
weeks.put("end", day.apply(now));
|
||||
weeks.put("start", twoWeeksAgo);
|
||||
weeks.put("midpoint", oneWeekAgo);
|
||||
weeks.put("end", now);
|
||||
|
||||
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo));
|
||||
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now));
|
||||
|
@ -175,9 +167,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now));
|
||||
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
|
||||
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount);
|
||||
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore));
|
||||
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter));
|
||||
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
|
||||
weeks.put("average_playtime_before", avgPlaytimeBefore);
|
||||
weeks.put("average_playtime_after", avgPlaytimeAfter);
|
||||
weeks.put("average_playtime_trend", avgPlaytimeTrend);
|
||||
|
||||
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo));
|
||||
|
@ -189,9 +181,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
|
|||
|
||||
long avgSessionLengthBefore = sessionsBefore != 0 ? playtimeBefore / sessionsBefore : 0;
|
||||
long avgSessionLengthAfter = sessionsAfter != 0 ? playtimeAfter / sessionsAfter : 0;
|
||||
Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false, timeAmount);
|
||||
weeks.put("session_length_average_before", timeAmount.apply(avgSessionLengthBefore));
|
||||
weeks.put("session_length_average_after", timeAmount.apply(avgSessionLengthAfter));
|
||||
Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false);
|
||||
weeks.put("session_length_average_before", avgSessionLengthBefore);
|
||||
weeks.put("session_length_average_after", avgSessionLengthAfter);
|
||||
weeks.put("session_length_average_trend", avgSessionLengthTrend);
|
||||
|
||||
return weeks;
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver;
|
||||
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Simple guard against DDoS attacks to single endpoint.
|
||||
* <p>
|
||||
* This only protects against a DDoS that doesn't follow redirects.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RateLimitGuard {
|
||||
|
||||
private static final int ATTEMPT_LIMIT = 30;
|
||||
private final Cache<String, Integer> requests = Caffeine.newBuilder()
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
private final Cache<String, String> lastRequestPath = Caffeine.newBuilder()
|
||||
.expireAfterWrite(120, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
public boolean shouldPreventRequest(@Untrusted String requestPath, @Untrusted String accessor) {
|
||||
String previous = lastRequestPath.getIfPresent(accessor);
|
||||
if (!Objects.equals(previous, requestPath)) {
|
||||
resetAttemptCount(accessor);
|
||||
}
|
||||
|
||||
Integer attempts = requests.getIfPresent(accessor);
|
||||
if (attempts == null) {
|
||||
attempts = 0;
|
||||
}
|
||||
|
||||
lastRequestPath.put(accessor, requestPath);
|
||||
requests.put(accessor, attempts + 1);
|
||||
|
||||
// Too many attempts, forbid further attempts.
|
||||
return attempts + 1 >= ATTEMPT_LIMIT;
|
||||
}
|
||||
|
||||
public void resetAttemptCount(@Untrusted String accessor) {
|
||||
// previous request changed
|
||||
requests.invalidate(accessor);
|
||||
requests.cleanUp();
|
||||
}
|
||||
|
||||
public static class Disabled extends RateLimitGuard {
|
||||
@Override
|
||||
public boolean shouldPreventRequest(@Untrusted String requestedPath, String accessor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAttemptCount(String accessor) { /* Disabled */ }
|
||||
}
|
||||
|
||||
}
|
|
@ -475,6 +475,14 @@ public class ResponseFactory {
|
|||
.build();
|
||||
}
|
||||
|
||||
public Response failedRateLimit403() {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent("<h1>403 Forbidden</h1><p>You are being rate-limited.</p>")
|
||||
.setStatus(403)
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response ipWhitelist403(@Untrusted String accessor) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
|
|
|
@ -42,7 +42,6 @@ public enum DataID {
|
|||
GRAPH_ACTIVITY,
|
||||
GRAPH_PING,
|
||||
GRAPH_SERVER_PIE,
|
||||
GRAPH_HOSTNAME_PIE,
|
||||
GRAPH_PUNCHCARD,
|
||||
SERVER_OVERVIEW,
|
||||
ONLINE_OVERVIEW,
|
||||
|
@ -54,13 +53,28 @@ public enum DataID {
|
|||
EXTENSION_TABS,
|
||||
EXTENSION_JSON,
|
||||
LIST_SERVERS,
|
||||
JOIN_ADDRESSES_BY_DAY,
|
||||
JOIN_ADDRESSES_BY_DAY(false),
|
||||
PLAYER_RETENTION,
|
||||
PLAYER_JOIN_ADDRESSES,
|
||||
PLAYER_ALLOWLIST_BOUNCES,
|
||||
;
|
||||
|
||||
private final boolean cacheable;
|
||||
|
||||
DataID() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
DataID(boolean cacheable) {
|
||||
this.cacheable = cacheable;
|
||||
}
|
||||
|
||||
public boolean isCacheable() {
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
public String of(ServerUUID serverUUID) {
|
||||
if (serverUUID == null) return name();
|
||||
return name() + '-' + serverUUID;
|
||||
return name() + "_" + serverUUID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,10 @@ public interface JSONStorage extends SubSystem {
|
|||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public static StoredJSON fromObject(Object json, long timestamp) {
|
||||
return new StoredJSON(new Gson().toJson(json), timestamp);
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ public class WebserverLogMessages {
|
|||
}
|
||||
|
||||
public void warnWebserverDisabledByConfig() {
|
||||
logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED));
|
||||
logger.info(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED));
|
||||
}
|
||||
|
||||
public void keystoreNotFoundError(InvalidPathException error, String keyStorePath) {
|
||||
|
|
|
@ -75,8 +75,14 @@ public class AccessLogger {
|
|||
}
|
||||
}
|
||||
try {
|
||||
long timestamp = internalRequest.getTimestamp();
|
||||
String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||
String method = internalRequest.getMethod();
|
||||
method = method != null ? method : "?";
|
||||
String url = StoreRequestTransaction.getTruncatedURI(request, internalRequest);
|
||||
int responseCode = response.getCode();
|
||||
dbSystem.getDatabase().executeTransaction(
|
||||
new StoreRequestTransaction(webserverConfiguration, internalRequest, request, response)
|
||||
new StoreRequestTransaction(timestamp, accessAddress, method, url, responseCode)
|
||||
);
|
||||
} catch (CompletionException | DBOpException e) {
|
||||
errorLogger.warn(e, ErrorContext.builder()
|
||||
|
|
|
@ -81,4 +81,6 @@ public interface InternalRequest {
|
|||
}
|
||||
return authenticationExtractor.extractAuthentication(this);
|
||||
}
|
||||
|
||||
String getRequestedPath();
|
||||
}
|
||||
|
|
|
@ -129,6 +129,11 @@ public class JettyInternalRequest implements InternalRequest {
|
|||
return baseRequest.getRequestURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestedPath() {
|
||||
return baseRequest.getHttpURI().getDecodedPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JettyInternalRequest{" +
|
||||
|
|
|
@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver.http;
|
|||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard;
|
||||
import com.djrapitops.plan.delivery.webserver.RateLimitGuard;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseResolver;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
|
@ -40,6 +41,7 @@ public class RequestHandler {
|
|||
private final ResponseResolver responseResolver;
|
||||
|
||||
private final PassBruteForceGuard bruteForceGuard;
|
||||
private final RateLimitGuard rateLimitGuard;
|
||||
private final AccessLogger accessLogger;
|
||||
|
||||
@Inject
|
||||
|
@ -50,15 +52,22 @@ public class RequestHandler {
|
|||
this.accessLogger = accessLogger;
|
||||
|
||||
bruteForceGuard = new PassBruteForceGuard();
|
||||
rateLimitGuard = new RateLimitGuard();
|
||||
}
|
||||
|
||||
public Response getResponse(InternalRequest internalRequest) {
|
||||
@Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
|
||||
@Untrusted String requestedPath = internalRequest.getRequestedPath();
|
||||
|
||||
boolean blocked = false;
|
||||
Response response;
|
||||
@Untrusted Request request = null;
|
||||
if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
|
||||
response = responseFactory.failedLoginAttempts403();
|
||||
blocked = true;
|
||||
} else if (rateLimitGuard.shouldPreventRequest(requestedPath, accessAddress)) {
|
||||
response = responseFactory.failedRateLimit403();
|
||||
blocked = true;
|
||||
} else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
|
||||
webserverConfiguration.getWebserverLogMessages()
|
||||
.warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString());
|
||||
|
@ -77,7 +86,9 @@ public class RequestHandler {
|
|||
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
|
||||
response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow");
|
||||
|
||||
accessLogger.log(internalRequest, request, response);
|
||||
if (!blocked) {
|
||||
accessLogger.log(internalRequest, request, response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.AllowlistQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Response resolver to get game allowlist bounces.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
@Path("/v1/gameAllowlistBounces")
|
||||
public class AllowlistJSONResolver extends JSONResolver {
|
||||
|
||||
private final DBSystem dbSystem;
|
||||
private final Identifiers identifiers;
|
||||
private final AsyncJSONResolverService jsonResolverService;
|
||||
|
||||
@Inject
|
||||
public AllowlistJSONResolver(DBSystem dbSystem, Identifiers identifiers, AsyncJSONResolverService jsonResolverService) {
|
||||
this.dbSystem = dbSystem;
|
||||
this.identifiers = identifiers;
|
||||
this.jsonResolverService = jsonResolverService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(@Untrusted Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
description = "Get allowlist bounce data for server",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
|
||||
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
|
||||
},
|
||||
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
||||
@ExampleObject("Server 1"),
|
||||
@ExampleObject("1"),
|
||||
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
|
||||
}),
|
||||
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
|
||||
)
|
||||
@Override
|
||||
public Optional<Response> resolve(@Untrusted Request request) {
|
||||
return Optional.of(getResponse(request));
|
||||
}
|
||||
|
||||
private Response getResponse(@Untrusted Request request) {
|
||||
JSONStorage.StoredJSON result = getStoredJSON(request);
|
||||
return getCachedOrNewResponse(request, result);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JSONStorage.StoredJSON getStoredJSON(Request request) {
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||
Database database = dbSystem.getDatabase();
|
||||
return jsonResolverService.resolve(timestamp, DataID.PLAYER_ALLOWLIST_BOUNCES, serverUUID,
|
||||
theUUID -> Map.of(
|
||||
"allowlist_bounces", database.query(AllowlistQueries.getBounces(serverUUID)),
|
||||
"last_seen_by_uuid", database.query(SessionQueries.lastSeen(serverUUID))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -39,11 +39,16 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
|||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Resolves /v1/graph JSON requests.
|
||||
|
@ -117,7 +122,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
@ExampleObject("aggregatedPing"),
|
||||
@ExampleObject("punchCard"),
|
||||
@ExampleObject("serverPie"),
|
||||
@ExampleObject("joinAddressPie"),
|
||||
@ExampleObject("joinAddressByDay"),
|
||||
}),
|
||||
@Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = {
|
||||
|
@ -156,15 +160,22 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
JSONStorage.StoredJSON storedJSON;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, serverUUID,
|
||||
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery())
|
||||
);
|
||||
Function<ServerUUID, Object> generationFunction = theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery());
|
||||
if (dataID.isCacheable()) {
|
||||
storedJSON = jsonResolverService.resolve(timestamp, dataID, serverUUID, generationFunction);
|
||||
} else {
|
||||
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.apply(serverUUID), System.currentTimeMillis());
|
||||
}
|
||||
} else {
|
||||
// Assume network
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery())
|
||||
);
|
||||
Supplier<Object> generationFunction = () -> generateGraphDataJSONOfType(dataID, request.getQuery());
|
||||
if (dataID.isCacheable()) {
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, generationFunction
|
||||
);
|
||||
} else {
|
||||
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.get(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
return storedJSON;
|
||||
}
|
||||
|
@ -197,8 +208,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return DataID.GRAPH_PUNCHCARD;
|
||||
case "serverPie":
|
||||
return DataID.GRAPH_SERVER_PIE;
|
||||
case "joinAddressPie":
|
||||
return DataID.GRAPH_HOSTNAME_PIE;
|
||||
case "joinAddressByDay":
|
||||
return DataID.JOIN_ADDRESSES_BY_DAY;
|
||||
default:
|
||||
|
@ -229,8 +238,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
|
||||
case GRAPH_WORLD_MAP:
|
||||
return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP);
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE);
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
default:
|
||||
|
@ -258,8 +265,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
|
||||
case GRAPH_ONLINE_PROXIES:
|
||||
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE);
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
default:
|
||||
|
@ -283,8 +288,6 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return graphJSON.serverCalendarJSON(serverUUID);
|
||||
case GRAPH_WORLD_PIE:
|
||||
return graphJSON.serverWorldPieJSONAsMap(serverUUID);
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return graphJSON.playerHostnamePieJSONAsMap(serverUUID);
|
||||
case GRAPH_ACTIVITY:
|
||||
return graphJSON.activityGraphsJSONAsMap(serverUUID);
|
||||
case GRAPH_WORLD_MAP:
|
||||
|
@ -294,19 +297,24 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
case GRAPH_PUNCHCARD:
|
||||
return graphJSON.punchCardJSONAsMap(serverUUID);
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
try {
|
||||
return graphJSON.joinAddressesByDay(serverUUID,
|
||||
query.get("after").map(Long::parseLong).orElse(0L),
|
||||
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
||||
);
|
||||
} catch (@Untrusted NumberFormatException e) {
|
||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||
}
|
||||
return joinAddressGraph(serverUUID, query);
|
||||
default:
|
||||
throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> joinAddressGraph(ServerUUID serverUUID, @Untrusted URIQuery query) {
|
||||
try {
|
||||
Long after = query.get("after").map(Long::parseLong).orElse(0L);
|
||||
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
|
||||
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
|
||||
.map(Arrays::asList).orElse(List.of());
|
||||
return graphJSON.joinAddressesByDay(serverUUID, after, before, addressFilter);
|
||||
} catch (@Untrusted NumberFormatException e) {
|
||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||
}
|
||||
}
|
||||
|
||||
private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) {
|
||||
switch (id) {
|
||||
case GRAPH_ACTIVITY:
|
||||
|
@ -319,23 +327,26 @@ public class GraphsJSONResolver extends JSONResolver {
|
|||
return graphJSON.networkCalendarJSON();
|
||||
case GRAPH_SERVER_PIE:
|
||||
return graphJSON.serverPreferencePieJSONAsMap();
|
||||
case GRAPH_HOSTNAME_PIE:
|
||||
return graphJSON.playerHostnamePieJSONAsMap();
|
||||
case GRAPH_WORLD_MAP:
|
||||
return graphJSON.geolocationGraphsJSONAsMap();
|
||||
case GRAPH_ONLINE_PROXIES:
|
||||
return graphJSON.proxyPlayersOnlineGraphs();
|
||||
case JOIN_ADDRESSES_BY_DAY:
|
||||
try {
|
||||
return graphJSON.joinAddressesByDay(
|
||||
query.get("after").map(Long::parseLong).orElse(0L),
|
||||
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
|
||||
);
|
||||
} catch (@Untrusted NumberFormatException e) {
|
||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||
}
|
||||
return joinAddressGraph(query);
|
||||
default:
|
||||
throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> joinAddressGraph(URIQuery query) {
|
||||
try {
|
||||
Long after = query.get("after").map(Long::parseLong).orElse(0L);
|
||||
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
|
||||
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
|
||||
.map(Arrays::asList).orElse(List.of());
|
||||
return graphJSON.joinAddressesByDay(after, before, addressFilter);
|
||||
} catch (@Untrusted NumberFormatException e) {
|
||||
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,11 +17,13 @@
|
|||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
|
@ -34,6 +36,7 @@ import io.swagger.v3.oas.annotations.Parameter;
|
|||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import jakarta.ws.rs.GET;
|
||||
|
@ -42,7 +45,6 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
|
@ -69,17 +71,27 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
|
|||
@Override
|
||||
public boolean canAccess(@Untrusted Request request) {
|
||||
WebUser user = request.getUser().orElse(new WebUser(""));
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
|
||||
@Untrusted URIQuery query = request.getQuery();
|
||||
Optional<String> listOnly = query.get("listOnly");
|
||||
if (query.get("server").isPresent()) {
|
||||
if (listOnly.isEmpty()) {
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
|
||||
} else {
|
||||
return user.hasPermission(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
}
|
||||
}
|
||||
if (listOnly.isEmpty()) {
|
||||
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
|
||||
} else {
|
||||
return user.hasPermission(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
|
||||
}
|
||||
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(
|
||||
description = "Get join address information of players for server or network",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
|
||||
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = PlayerJoinAddresses.class))),
|
||||
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
|
||||
},
|
||||
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
|
||||
|
@ -105,12 +117,12 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
|
|||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID,
|
||||
theUUID -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses(theUUID))
|
||||
serverUUID1 -> jsonFactory.playerJoinAddresses(serverUUID1, request.getQuery().get("listOnly").isEmpty())
|
||||
);
|
||||
}
|
||||
// Assume network
|
||||
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES,
|
||||
() -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses())
|
||||
() -> jsonFactory.playerJoinAddresses(request.getQuery().get("listOnly").isEmpty())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ public class RootJSONResolver {
|
|||
RetentionJSONResolver retentionJSONResolver,
|
||||
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
|
||||
PluginHistoryJSONResolver pluginHistoryJSONResolver,
|
||||
AllowlistJSONResolver allowlistJSONResolver,
|
||||
|
||||
PreferencesJSONResolver preferencesJSONResolver,
|
||||
StorePreferencesJSONResolver storePreferencesJSONResolver,
|
||||
|
@ -129,7 +130,8 @@ public class RootJSONResolver {
|
|||
.add("extensionData", extensionJSONResolver)
|
||||
.add("retention", retentionJSONResolver)
|
||||
.add("joinAddresses", playerJoinAddressJSONResolver)
|
||||
.add("preferences", preferencesJSONResolver);
|
||||
.add("preferences", preferencesJSONResolver)
|
||||
.add("gameAllowlistBounces", allowlistJSONResolver);
|
||||
|
||||
this.webServer = webServer;
|
||||
// These endpoints require authentication to be enabled.
|
||||
|
|
|
@ -114,7 +114,7 @@ public class FiltersJSONResolver implements Resolver {
|
|||
)).build();
|
||||
}
|
||||
|
||||
private List<Double[]> fetchViewGraphPoints() {
|
||||
private List<Number[]> fetchViewGraphPoints() {
|
||||
List<DateObj<Integer>> data = dbSystem.getDatabase().query(TPSQueries.fetchViewPreviewGraphData(serverInfo.getServerUUID()));
|
||||
Long earliestStart = dbSystem.getDatabase().query(SessionQueries.earliestSessionStart());
|
||||
data.add(0, new DateObj<>(earliestStart, 1));
|
||||
|
@ -136,9 +136,9 @@ public class FiltersJSONResolver implements Resolver {
|
|||
class FilterResponseDto {
|
||||
final List<FilterDto> filters;
|
||||
final ViewDto view;
|
||||
final List<Double[]> viewPoints;
|
||||
final List<Number[]> viewPoints;
|
||||
|
||||
public FilterResponseDto(Map<String, Filter> filtersByKind, ViewDto view, List<Double[]> viewPoints) {
|
||||
public FilterResponseDto(Map<String, Filter> filtersByKind, ViewDto view, List<Number[]> viewPoints) {
|
||||
this.viewPoints = viewPoints;
|
||||
this.filters = new ArrayList<>();
|
||||
for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) {
|
||||
|
|
|
@ -37,6 +37,8 @@ import javax.inject.Inject;
|
|||
import javax.inject.Singleton;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Endpoint for getting list of available Plan web permissions.
|
||||
|
@ -75,7 +77,10 @@ public class WebPermissionJSONResolver implements Resolver {
|
|||
}
|
||||
|
||||
private Response getResponse() {
|
||||
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions());
|
||||
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions())
|
||||
.stream()
|
||||
.filter(Predicate.not(WebPermission::isDeprecated))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
WebPermissionList permissionList = new WebPermissionList(permissions);
|
||||
return Response.builder()
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Optional;
|
|||
public class DBOpException extends IllegalStateException implements ExceptionWithContext {
|
||||
|
||||
public static final String CONSTRAINT_VIOLATION = "Constraint Violation";
|
||||
public static final String DUPLICATE_KEY = "Duplicate key";
|
||||
private final ErrorContext context;
|
||||
|
||||
public DBOpException(String message) {
|
||||
|
@ -77,7 +78,7 @@ public class DBOpException extends IllegalStateException implements ExceptionWit
|
|||
case 1022:
|
||||
case 23001:
|
||||
case 23505:
|
||||
context.related("Duplicate key")
|
||||
context.related(DUPLICATE_KEY)
|
||||
.whatToDo("Report this, duplicate key exists in SQL.");
|
||||
break;
|
||||
// Constraint violation
|
||||
|
@ -165,4 +166,9 @@ public class DBOpException extends IllegalStateException implements ExceptionWit
|
|||
&& getCause() != null
|
||||
&& getCause().getMessage().contains("user_id");
|
||||
}
|
||||
|
||||
public boolean isDuplicateKeyViolation() {
|
||||
return context != null
|
||||
&& context.getRelated().contains(DBOpException.CONSTRAINT_VIOLATION);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Executable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Transaction to remove older results that violate an updated condition value.
|
||||
* <p>
|
||||
* How it works:
|
||||
* - Select all fulfilled conditions for all players (conditionName when true and not_conditionName when false)
|
||||
* - Left join with player value and provider tables when uuids match, and when condition matches a condition in the query above.
|
||||
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
|
||||
* - Delete all player values with IDs that are returned by the left join query after filtering
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RemoveUnsatisfiedConditionalPlayerResultsTransaction extends ThrowawayTransaction {
|
||||
|
||||
private final String providerTable;
|
||||
private final String playerValueTable;
|
||||
private final String playerTableValueTable;
|
||||
private final String tableTable;
|
||||
private final String groupTable;
|
||||
|
||||
public RemoveUnsatisfiedConditionalPlayerResultsTransaction() {
|
||||
providerTable = ExtensionProviderTable.TABLE_NAME;
|
||||
playerValueTable = ExtensionPlayerValueTable.TABLE_NAME;
|
||||
tableTable = ExtensionTableProviderTable.TABLE_NAME;
|
||||
groupTable = ExtensionGroupsTable.TABLE_NAME;
|
||||
playerTableValueTable = ExtensionPlayerTableValueTable.TABLE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
String selectSatisfiedConditions = getSatisfiedConditionsSQL();
|
||||
|
||||
execute(deleteUnsatisfiedValues(selectSatisfiedConditions));
|
||||
execute(deleteUnsatisfiedGroupValues(selectSatisfiedConditions));
|
||||
execute(deleteUnsatisfiedTableValues(selectSatisfiedConditions));
|
||||
}
|
||||
|
||||
private String getSatisfiedConditionsSQL() {
|
||||
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
|
||||
|
||||
String selectSatisfiedPositiveConditions = SELECT +
|
||||
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionProviderTable.PLUGIN_ID + ',' +
|
||||
ExtensionPlayerTableValueTable.USER_UUID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
|
||||
String selectSatisfiedNegativeConditions = SELECT +
|
||||
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionProviderTable.PLUGIN_ID + ',' +
|
||||
ExtensionPlayerTableValueTable.USER_UUID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
|
||||
|
||||
// Query contents: Set of provided_conditions
|
||||
return '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedValues(String selectSatisfiedConditions) {
|
||||
// Query contents:
|
||||
// id | uuid | q1.uuid | condition | q1.provided_condition
|
||||
// -- | ---- | ------- | --------- | ---------------------
|
||||
// 1 | ... | ... | A | A Satisfied condition
|
||||
// 2 | ... | ... | not_B | not_B Satisfied condition
|
||||
// 3 | ... | ... | NULL | NULL Satisfied condition
|
||||
// 4 | ... | ... | B | NULL Unsatisfied condition, filtered to these in WHERE clause.
|
||||
// 5 | ... | ... | not_C | NULL Unsatisfied condition
|
||||
String selectUnsatisfiedValueIDs = SELECT + playerValueTable + '.' + ExtensionPlayerValueTable.ID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
|
||||
" on (" + // Join when uuid and plugin_id match and condition for the group provider is satisfied
|
||||
playerValueTable + '.' + ExtensionPlayerValueTable.USER_UUID +
|
||||
"=q1." + ExtensionPlayerValueTable.USER_UUID +
|
||||
AND + ExtensionProviderTable.CONDITION +
|
||||
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID +
|
||||
"=q1." + ExtensionProviderTable.PLUGIN_ID +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
|
||||
|
||||
// Nested query here is required because MySQL limits update statements with nested queries:
|
||||
// The nested query creates a temporary table that bypasses the same table query-update limit.
|
||||
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
|
||||
String sql = DELETE_FROM + playerValueTable +
|
||||
WHERE + ExtensionPlayerValueTable.ID + " IN (" + SELECT + ExtensionPlayerValueTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
|
||||
|
||||
return new ExecStatement(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedTableValues(String selectSatisfiedConditions) {
|
||||
String selectUnsatisfiedValueIDs = SELECT + ExtensionTableProviderTable.ID +
|
||||
FROM + tableTable +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
|
||||
" on (" + // Join when plugin_id matches and condition for the group provider is satisfied
|
||||
tableTable + '.' + ExtensionTableProviderTable.CONDITION +
|
||||
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
AND + tableTable + '.' + ExtensionTableProviderTable.PLUGIN_ID +
|
||||
"=q1." + ExtensionProviderTable.PLUGIN_ID +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
|
||||
|
||||
// Nested query here is required because MySQL limits update statements with nested queries:
|
||||
// The nested query creates a temporary table that bypasses the same table query-update limit.
|
||||
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
|
||||
String deleteValuesSQL = DELETE_FROM + playerTableValueTable +
|
||||
WHERE + ExtensionPlayerTableValueTable.TABLE_ID + " IN (" + SELECT + ExtensionTableProviderTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
|
||||
|
||||
return new ExecStatement(deleteValuesSQL) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedGroupValues(String selectSatisfiedConditions) {
|
||||
// plan_extensions_player_groups.id is needed for removal of the correct row.
|
||||
// The id is known if group_id & uuid are known
|
||||
// -
|
||||
// Conditions are in plan_extensions_providers
|
||||
// selectSatisfiedConditions lists 'provided_condition' Strings
|
||||
String selectUnsatisfiedIDs = SELECT + groupTable + '.' + ID +
|
||||
FROM + groupTable +
|
||||
INNER_JOIN + providerTable + " on " + providerTable + '.' + ID + '=' + groupTable + '.' + ExtensionGroupsTable.PROVIDER_ID +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
|
||||
" on (" + // Join when uuid and plugin_id match and condition for the group provider is satisfied
|
||||
groupTable + '.' + P_UUID +
|
||||
"=q1." + P_UUID +
|
||||
AND + ExtensionProviderTable.CONDITION +
|
||||
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID +
|
||||
"=q1." + ExtensionProviderTable.PLUGIN_ID +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
|
||||
|
||||
// Nested query here is required because MySQL limits update statements with nested queries:
|
||||
// The nested query creates a temporary table that bypasses the same table query-update limit.
|
||||
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
|
||||
String deleteValuesSQL = DELETE_FROM + groupTable +
|
||||
WHERE + ID + " IN (" + SELECT + ID + FROM + '(' + selectUnsatisfiedIDs + ") as ids)";
|
||||
|
||||
return new ExecStatement(deleteValuesSQL) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerTableValueTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionTableProviderTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Executable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Transaction to remove older results that violate an updated condition value.
|
||||
* <p>
|
||||
* How it works:
|
||||
* - Select all fulfilled conditions for all servers (conditionName when true and not_conditionName when false)
|
||||
* - Left join with server value and provider tables when plugin_ids match, and when condition matches a condition in the
|
||||
* query above. (plugin_ids can be linked to servers)
|
||||
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
|
||||
* - Delete all server values with IDs that are returned by the left join query after filtering
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RemoveUnsatisfiedConditionalServerResultsTransaction extends ThrowawayTransaction {
|
||||
|
||||
private final String providerTable;
|
||||
private final String serverValueTable;
|
||||
private final String serverTableValueTable;
|
||||
private final String tableTable;
|
||||
|
||||
public RemoveUnsatisfiedConditionalServerResultsTransaction() {
|
||||
providerTable = ExtensionProviderTable.TABLE_NAME;
|
||||
serverValueTable = ExtensionServerValueTable.TABLE_NAME;
|
||||
tableTable = ExtensionTableProviderTable.TABLE_NAME;
|
||||
serverTableValueTable = ExtensionServerTableValueTable.TABLE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
String selectSatisfiedConditions = getSatisfiedConditionsSQL();
|
||||
execute(deleteUnsatisfiedValues(selectSatisfiedConditions));
|
||||
execute(deleteUnsatisfiedTableValues(selectSatisfiedConditions));
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedValues(String selectSatisfiedConditions) {
|
||||
// Query contents:
|
||||
// id | provider_id | q1.provider_id | condition | q1.provided_condition
|
||||
// -- | ----------- | -------------- | --------- | ---------------------
|
||||
// 1 | ... | ... | A | A Satisfied condition
|
||||
// 2 | ... | ... | not_B | not_B Satisfied condition
|
||||
// 3 | ... | ... | NULL | NULL Satisfied condition
|
||||
// 4 | ... | ... | B | NULL Unsatisfied condition, filtered to these in WHERE clause.
|
||||
// 5 | ... | ... | not_C | NULL Unsatisfied condition
|
||||
String selectUnsatisfiedValueIDs = SELECT + serverValueTable + '.' + ExtensionServerValueTable.ID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
|
||||
" on (" +
|
||||
ExtensionProviderTable.CONDITION + "=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID + "=q1." + ExtensionProviderTable.PLUGIN_ID +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
|
||||
|
||||
// Nested query here is required because MySQL limits update statements with nested queries:
|
||||
// The nested query creates a temporary table that bypasses the same table query-update limit.
|
||||
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
|
||||
String sql = DELETE_FROM + serverValueTable +
|
||||
WHERE + ExtensionServerValueTable.ID + " IN (" + SELECT + ExtensionServerValueTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
|
||||
|
||||
return new ExecStatement(sql) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getSatisfiedConditionsSQL() {
|
||||
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
|
||||
|
||||
String selectSatisfiedPositiveConditions = SELECT +
|
||||
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionProviderTable.PLUGIN_ID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionServerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
|
||||
String selectSatisfiedNegativeConditions = SELECT +
|
||||
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
|
||||
ExtensionProviderTable.PLUGIN_ID +
|
||||
FROM + providerTable +
|
||||
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
|
||||
WHERE + ExtensionServerValueTable.BOOLEAN_VALUE + "=?" +
|
||||
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
|
||||
|
||||
// Query contents: Set of provided_conditions
|
||||
return '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedTableValues(String selectSatisfiedConditions) {
|
||||
String selectUnsatisfiedValueIDs = SELECT + ExtensionTableProviderTable.ID +
|
||||
FROM + tableTable +
|
||||
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
|
||||
" on (" +
|
||||
tableTable + '.' + ExtensionTableProviderTable.CONDITION +
|
||||
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
|
||||
AND + tableTable + '.' + ExtensionTableProviderTable.PLUGIN_ID +
|
||||
"=q1." + ExtensionProviderTable.PLUGIN_ID +
|
||||
')' +
|
||||
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
|
||||
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
|
||||
|
||||
// Nested query here is required because MySQL limits update statements with nested queries:
|
||||
// The nested query creates a temporary table that bypasses the same table query-update limit.
|
||||
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
|
||||
String deleteValuesSQL = DELETE_FROM + serverTableValueTable +
|
||||
WHERE + ExtensionServerTableValueTable.TABLE_ID + " IN (" + SELECT + ExtensionTableProviderTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
|
||||
|
||||
return new ExecStatement(deleteValuesSQL) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setBoolean(1, true); // Select provided conditions with 'true' value
|
||||
statement.setBoolean(2, false); // Select negated conditions with 'false' value
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -19,13 +19,20 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.result
|
|||
import com.djrapitops.plan.extension.implementation.ProviderInformation;
|
||||
import com.djrapitops.plan.extension.implementation.providers.Parameters;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.queries.QueryStatement;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Executable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.AND;
|
||||
|
@ -61,6 +68,13 @@ public class StorePlayerBooleanResultTransaction extends ThrowawayTransaction {
|
|||
@Override
|
||||
protected void performOperations() {
|
||||
execute(storeValue());
|
||||
commitMidTransaction();
|
||||
List<Integer> providerIds = selectUnfulfilledProviderIds();
|
||||
if (!providerIds.isEmpty()) {
|
||||
execute(deleteUnsatisfiedConditionalResults(providerIds));
|
||||
execute(deleteUnsatisfiedConditionalGroups(providerIds));
|
||||
}
|
||||
execute(deleteUnsatisfiedConditionalTables());
|
||||
}
|
||||
|
||||
private Executable storeValue() {
|
||||
|
@ -104,4 +118,92 @@ public class StorePlayerBooleanResultTransaction extends ThrowawayTransaction {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedConditionalResults(List<Integer> providerIds) {
|
||||
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_user_values " +
|
||||
"WHERE uuid=? " +
|
||||
"AND provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
|
||||
|
||||
return deleteIds(providerIds, deleteUnsatisfiedValues);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ExecStatement deleteIds(List<Integer> providerIds, @Language("SQL") String deleteUnsatisfiedValues) {
|
||||
return new ExecStatement(deleteUnsatisfiedValues) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, playerUUID.toString());
|
||||
for (int i = 0; i < providerIds.size(); i++) {
|
||||
statement.setInt(i + 2, providerIds.get(i));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedConditionalGroups(List<Integer> providerIds) {
|
||||
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_groups " +
|
||||
"WHERE uuid=? " +
|
||||
"AND provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
|
||||
|
||||
return deleteIds(providerIds, deleteUnsatisfiedValues);
|
||||
}
|
||||
|
||||
private List<Integer> selectUnfulfilledProviderIds() {
|
||||
// Need to select:
|
||||
// Provider IDs where condition of this provider is met
|
||||
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
|
||||
"FROM plan_extension_providers indb " +
|
||||
"JOIN plan_extension_providers unfulfilled ON unfulfilled.condition_name=" +
|
||||
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
|
||||
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
|
||||
" AND indb.plugin_id=unfulfilled.plugin_id" +
|
||||
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
|
||||
" AND indb.provided_condition IS NOT NULL";
|
||||
|
||||
return extractIds(selectUnsatisfiedProviderIds);
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedConditionalTables() {
|
||||
List<Integer> tableIds = selectUnfulfilledTableIds();
|
||||
if (tableIds.isEmpty()) return Executable.empty();
|
||||
|
||||
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_user_table_values " +
|
||||
"WHERE uuid=? " +
|
||||
"AND table_id IN (" + Sql.nParameters(tableIds.size()) + ")";
|
||||
|
||||
return deleteIds(tableIds, deleteUnsatisfiedValues);
|
||||
}
|
||||
|
||||
private List<Integer> selectUnfulfilledTableIds() {
|
||||
// Need to select:
|
||||
// Provider IDs where condition of this provider is met
|
||||
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
|
||||
"FROM plan_extension_providers indb " +
|
||||
"JOIN plan_extension_tables unfulfilled ON unfulfilled.condition_name=" +
|
||||
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
|
||||
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
|
||||
" AND indb.plugin_id=unfulfilled.plugin_id" +
|
||||
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
|
||||
" AND indb.provided_condition IS NOT NULL";
|
||||
|
||||
return extractIds(selectUnsatisfiedProviderIds);
|
||||
}
|
||||
|
||||
private List<Integer> extractIds(@Language("SQL") String selectUnsatisfiedProviderIds) {
|
||||
return query(new QueryStatement<>(selectUnsatisfiedProviderIds) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
ExtensionProviderTable.set3PluginValuesToStatement(statement, 1, providerName, pluginName, serverUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> processResults(ResultSet set) throws SQLException {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
while (set.next()) {
|
||||
ids.add(set.getInt(1));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,13 +19,19 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.result
|
|||
import com.djrapitops.plan.extension.implementation.ProviderInformation;
|
||||
import com.djrapitops.plan.extension.implementation.providers.Parameters;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.queries.QueryStatement;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Executable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
|
||||
import static com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable.*;
|
||||
|
@ -61,6 +67,9 @@ public class StoreServerBooleanResultTransaction extends ThrowawayTransaction {
|
|||
@Override
|
||||
protected void performOperations() {
|
||||
execute(storeValue());
|
||||
commitMidTransaction();
|
||||
execute(deleteUnsatisfiedConditionalResults());
|
||||
execute(deleteUnsatisfiedConditionalTables());
|
||||
}
|
||||
|
||||
private Executable storeValue() {
|
||||
|
@ -100,4 +109,86 @@ public class StoreServerBooleanResultTransaction extends ThrowawayTransaction {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedConditionalResults() {
|
||||
List<Integer> providerIds = selectUnfulfilledProviderIds();
|
||||
if (providerIds.isEmpty()) return Executable.empty();
|
||||
|
||||
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_server_values " +
|
||||
"WHERE provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
|
||||
|
||||
return new ExecStatement(deleteUnsatisfiedValues) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (int i = 0; i < providerIds.size(); i++) {
|
||||
statement.setInt(i + 1, providerIds.get(i));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private List<Integer> selectUnfulfilledProviderIds() {
|
||||
// Need to select:
|
||||
// Provider IDs where condition of this provider is met
|
||||
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
|
||||
"FROM plan_extension_providers indb " +
|
||||
"JOIN plan_extension_providers unfulfilled ON unfulfilled.condition_name=" +
|
||||
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
|
||||
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
|
||||
" AND indb.plugin_id=unfulfilled.plugin_id" +
|
||||
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
|
||||
" AND indb.provided_condition IS NOT NULL";
|
||||
|
||||
return extractIds(selectUnsatisfiedProviderIds);
|
||||
}
|
||||
|
||||
private Executable deleteUnsatisfiedConditionalTables() {
|
||||
List<Integer> tableIds = selectUnfulfilledTableIds();
|
||||
if (tableIds.isEmpty()) return Executable.empty();
|
||||
|
||||
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_server_table_values " +
|
||||
"WHERE table_id IN (" + Sql.nParameters(tableIds.size()) + ")";
|
||||
|
||||
return new ExecStatement(deleteUnsatisfiedValues) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (int i = 0; i < tableIds.size(); i++) {
|
||||
statement.setInt(i + 1, tableIds.get(i));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private List<Integer> selectUnfulfilledTableIds() {
|
||||
// Need to select:
|
||||
// Provider IDs where condition of this provider is met
|
||||
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
|
||||
"FROM plan_extension_providers indb " +
|
||||
"JOIN plan_extension_tables unfulfilled ON unfulfilled.condition_name=" +
|
||||
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
|
||||
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
|
||||
" AND indb.plugin_id=unfulfilled.plugin_id" +
|
||||
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
|
||||
" AND indb.provided_condition IS NOT NULL";
|
||||
|
||||
return extractIds(selectUnsatisfiedProviderIds);
|
||||
}
|
||||
|
||||
private List<Integer> extractIds(@Language("SQL") String selectUnsatisfiedProviderIds) {
|
||||
return query(new QueryStatement<>(selectUnsatisfiedProviderIds) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
ExtensionProviderTable.set3PluginValuesToStatement(statement, 1, providerName, pluginName, serverUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> processResults(ResultSet set) throws SQLException {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
while (set.next()) {
|
||||
ids.add(set.getInt(1));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.gathering;
|
||||
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility for validating and sanitizing join addresses.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class JoinAddressValidator {
|
||||
|
||||
private final PlanConfig config;
|
||||
private List<String> filteredAddresses;
|
||||
|
||||
@Inject
|
||||
public JoinAddressValidator(PlanConfig config) {
|
||||
/* Dagger injection constructor */
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private void prepareFilteredAddresses() {
|
||||
if (filteredAddresses == null) {
|
||||
filteredAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
|
||||
if (filteredAddresses == null) filteredAddresses = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Untrusted
|
||||
public String sanitize(@Untrusted String address) {
|
||||
if (address == null || config.isFalse(DataGatheringSettings.JOIN_ADDRESSES)) return "";
|
||||
if (!address.isEmpty()) {
|
||||
// Remove port
|
||||
if (address.contains(":")) {
|
||||
address = address.substring(0, address.lastIndexOf(':'));
|
||||
}
|
||||
// Remove data added by Bungeecord/Velocity
|
||||
if (address.contains("\u0000")) {
|
||||
address = address.substring(0, address.indexOf('\u0000'));
|
||||
}
|
||||
// Remove data added by Forge Mod Loader
|
||||
if (address.contains("fml")) {
|
||||
address = address.substring(0, address.lastIndexOf("fml"));
|
||||
}
|
||||
if (config.isFalse(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE)) {
|
||||
address = StringUtils.lowerCase(address);
|
||||
}
|
||||
prepareFilteredAddresses();
|
||||
if (filteredAddresses.contains(address)) {
|
||||
address = "";
|
||||
}
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
public boolean isValid(@Untrusted String address) {
|
||||
if (address.isEmpty()) return false;
|
||||
if (config.isTrue(DataGatheringSettings.PRESERVE_INVALID_JOIN_ADDRESS)) return true;
|
||||
try {
|
||||
URI uri = new URI(address);
|
||||
String path = uri.getPath();
|
||||
return path != null && path.indexOf('.') != -1;
|
||||
} catch (URISyntaxException uriSyntaxException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -48,7 +48,6 @@ public abstract class ServerShutdownSave {
|
|||
private final ErrorLogger errorLogger;
|
||||
|
||||
private boolean shuttingDown = false;
|
||||
private boolean startedDatabase = false;
|
||||
|
||||
protected ServerShutdownSave(
|
||||
Locale locale,
|
||||
|
@ -90,7 +89,7 @@ public abstract class ServerShutdownSave {
|
|||
|
||||
private Optional<Future<?>> attemptSave(Collection<ActiveSession> activeSessions) {
|
||||
try {
|
||||
return Optional.of(saveActiveSessions(finishSessions(activeSessions, System.currentTimeMillis())));
|
||||
return saveActiveSessions(finishSessions(activeSessions, System.currentTimeMillis()));
|
||||
} catch (DBInitException e) {
|
||||
errorLogger.error(e, ErrorContext.builder()
|
||||
.whatToDo("Find the sessions in the error file and save them manually or ignore. Report & delete the error file after.")
|
||||
|
@ -101,20 +100,19 @@ public abstract class ServerShutdownSave {
|
|||
} catch (IllegalStateException ignored) {
|
||||
/* Database is not initialized */
|
||||
return Optional.empty();
|
||||
} finally {
|
||||
closeDatabase(dbSystem.getDatabase());
|
||||
}
|
||||
}
|
||||
|
||||
private Future<?> saveActiveSessions(Collection<FinishedSession> finishedSessions) {
|
||||
private Optional<Future<?>> saveActiveSessions(Collection<FinishedSession> finishedSessions) {
|
||||
Database database = dbSystem.getDatabase();
|
||||
if (database.getState() == Database.State.CLOSED) {
|
||||
// Ensure that database is not closed when performing the transaction.
|
||||
startedDatabase = true;
|
||||
database.init();
|
||||
// Don't attempt to save if database is closed, session storage will be handled by
|
||||
// ShutdownDataPreservation instead.
|
||||
// Previously database reboot was attempted, but this could lead to server hang.
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return saveSessions(finishedSessions, database);
|
||||
return Optional.of(saveSessions(finishedSessions, database));
|
||||
}
|
||||
|
||||
Collection<FinishedSession> finishSessions(Collection<ActiveSession> activeSessions, long now) {
|
||||
|
@ -127,10 +125,4 @@ public abstract class ServerShutdownSave {
|
|||
private Future<?> saveSessions(Collection<FinishedSession> finishedSessions, Database database) {
|
||||
return database.executeTransaction(new ServerShutdownTransaction(finishedSessions));
|
||||
}
|
||||
|
||||
private void closeDatabase(Database database) {
|
||||
if (startedDatabase) {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package com.djrapitops.plan.gathering.domain;
|
||||
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
@ -24,8 +26,10 @@ public interface PlatformPlayerData {
|
|||
|
||||
UUID getUUID();
|
||||
|
||||
@Untrusted
|
||||
String getName();
|
||||
|
||||
@Untrusted
|
||||
default Optional<String> getDisplayName() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -38,6 +42,7 @@ public interface PlatformPlayerData {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Untrusted
|
||||
default Optional<String> getJoinAddress() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
|
|
@ -64,6 +64,13 @@ public class PlayerJoin {
|
|||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address used to join the server.
|
||||
*
|
||||
* @return Join address of the player.
|
||||
* @deprecated {@link com.djrapitops.plan.gathering.JoinAddressValidator} should be used when looking at join address.
|
||||
*/
|
||||
@Deprecated(since = "2024-04-27")
|
||||
public String getJoinAddress() {
|
||||
return player.getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.domain.ServerName;
|
|||
import com.djrapitops.plan.delivery.export.Exporter;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.gathering.JoinAddressValidator;
|
||||
import com.djrapitops.plan.gathering.cache.NicknameCache;
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
|
@ -38,7 +39,6 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
|||
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -52,6 +52,7 @@ public class PlayerJoinEventConsumer {
|
|||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
private final JoinAddressValidator joinAddressValidator;
|
||||
private final GeolocationCache geolocationCache;
|
||||
private final SessionCache sessionCache;
|
||||
private final NicknameCache nicknameCache;
|
||||
|
@ -63,7 +64,7 @@ public class PlayerJoinEventConsumer {
|
|||
public PlayerJoinEventConsumer(
|
||||
Processing processing,
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
DBSystem dbSystem, JoinAddressValidator joinAddressValidator,
|
||||
GeolocationCache geolocationCache,
|
||||
SessionCache sessionCache,
|
||||
NicknameCache nicknameCache,
|
||||
|
@ -73,6 +74,7 @@ public class PlayerJoinEventConsumer {
|
|||
this.processing = processing;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.joinAddressValidator = joinAddressValidator;
|
||||
this.geolocationCache = geolocationCache;
|
||||
this.sessionCache = sessionCache;
|
||||
this.nicknameCache = nicknameCache;
|
||||
|
@ -110,7 +112,8 @@ public class PlayerJoinEventConsumer {
|
|||
|
||||
private void storeJoinAddress(PlayerJoin join) {
|
||||
join.getPlayer().getJoinAddress()
|
||||
.map(joinAddress -> config.isTrue(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE) ? joinAddress : StringUtils.lowerCase(joinAddress))
|
||||
.map(joinAddressValidator::sanitize)
|
||||
.filter(joinAddressValidator::isValid)
|
||||
.map(StoreJoinAddressTransaction::new)
|
||||
.ifPresent(dbSystem.getDatabase()::executeTransaction);
|
||||
}
|
||||
|
@ -141,7 +144,10 @@ public class PlayerJoinEventConsumer {
|
|||
|
||||
private CompletableFuture<?> storeGamePlayer(PlayerJoin join) {
|
||||
long registerDate = getRegisterDate(join);
|
||||
String joinAddress = join.getPlayer().getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
String joinAddress = join.getPlayer().getJoinAddress()
|
||||
.map(joinAddressValidator::sanitize)
|
||||
.filter(joinAddressValidator::isValid)
|
||||
.orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
Transaction transaction = new StoreServerPlayerTransaction(
|
||||
join.getPlayerUUID(), registerDate, join.getPlayer().getName(), join.getServerUUID(), joinAddress
|
||||
);
|
||||
|
@ -171,12 +177,16 @@ public class PlayerJoinEventConsumer {
|
|||
}
|
||||
|
||||
private ActiveSession mapToActiveSession(PlayerJoin join) {
|
||||
String joinAddress = join.getPlayer().getJoinAddress()
|
||||
.map(joinAddressValidator::sanitize)
|
||||
.filter(joinAddressValidator::isValid)
|
||||
.orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
ActiveSession session = new ActiveSession(join.getPlayerUUID(), join.getServerUUID(), join.getTime(),
|
||||
join.getPlayer().getCurrentWorld().orElse(null),
|
||||
join.getPlayer().getCurrentGameMode().orElse(null));
|
||||
session.getExtraData().put(PlayerName.class, new PlayerName(join.getPlayer().getName()));
|
||||
session.getExtraData().put(ServerName.class, new ServerName(join.getServer().isProxy() ? join.getServer().getName() : "Proxy Server"));
|
||||
session.getExtraData().put(JoinAddress.class, new JoinAddress(config.isTrue(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE) ? join.getJoinAddress() : StringUtils.lowerCase(join.getJoinAddress())));
|
||||
session.getExtraData().put(JoinAddress.class, new JoinAddress(joinAddress));
|
||||
return session;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,15 +20,13 @@ import com.djrapitops.plan.exceptions.PreparationException;
|
|||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.Base64Util;
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.maxmind.geoip2.model.CountryResponse;
|
||||
import com.maxmind.geoip2.record.Country;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.compress.utils.IOUtils;
|
||||
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -38,10 +36,10 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
|
@ -83,62 +81,45 @@ public class GeoLite2Geolocator implements Geolocator {
|
|||
Files.delete(geolocationDB.toPath()); // Delete old data according to restriction 3. in EULA
|
||||
}
|
||||
}
|
||||
|
||||
downloadDatabase();
|
||||
// Delete old Geolocation database file if it still exists (on success to avoid a no-file situation)
|
||||
Files.deleteIfExists(files.getFileFromPluginFolder("GeoIP.dat").toPath());
|
||||
}
|
||||
|
||||
private static String a(String c, String d) {
|
||||
var o = new StandardPBEStringEncryptor();
|
||||
g(c, q(o));
|
||||
return o.decrypt(d);
|
||||
}
|
||||
|
||||
private static void g(String h, Consumer<String> b) {
|
||||
b.accept(l(h));
|
||||
}
|
||||
|
||||
private static Consumer<String> q(StandardPBEStringEncryptor t) {
|
||||
return t::setPassword;
|
||||
}
|
||||
|
||||
private static String l(String f) {
|
||||
return Base64Util.decode(f);
|
||||
}
|
||||
|
||||
private void downloadDatabase() throws IOException {
|
||||
// Avoid Socket leak with the parameters in case download url has proxy
|
||||
// https://AuroraLS3.github.io/mishaps/java_socket_leak_incident
|
||||
Properties properties = System.getProperties();
|
||||
properties.setProperty("sun.net.client.defaultConnectTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L)));
|
||||
properties.setProperty("sun.net.client.defaultReadTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L)));
|
||||
properties.setProperty("sun.net.http.retryPost", Boolean.toString(false));
|
||||
|
||||
String key = getKey();
|
||||
String downloadFrom = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=" + key + "&suffix=tar.gz";
|
||||
URL downloadSite = new URL(downloadFrom);
|
||||
try (
|
||||
InputStream in = downloadSite.openStream();
|
||||
GZIPInputStream gzipIn = new GZIPInputStream(in);
|
||||
TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn);
|
||||
FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile())
|
||||
) {
|
||||
findAndCopyFromTar(tarIn, fos);
|
||||
String downloadURL = config.get(DataGatheringSettings.GEOLOCATION_DOWNLOAD_URL);
|
||||
URL downloadSite = new URL(downloadURL);
|
||||
if (downloadURL.startsWith("https://download.maxmind.com/app/geoip_download")) {
|
||||
try (
|
||||
InputStream in = downloadSite.openStream();
|
||||
GZIPInputStream gzipIn = new GZIPInputStream(in);
|
||||
TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn);
|
||||
FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile())
|
||||
) {
|
||||
findAndCopyFromTar(tarIn, fos);
|
||||
}
|
||||
} else {
|
||||
URLConnection connection = downloadSite.openConnection();
|
||||
connection.setRequestProperty("X-PLAN-GEODB-TOKEN", "68342d1f-5fc9-4853-bd1e-ba88c466b3a6");
|
||||
try (
|
||||
InputStream in = connection.getInputStream();
|
||||
FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile())
|
||||
) {
|
||||
IOUtils.copy(in, fos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getKey() throws IOException {
|
||||
String y = "bGljZW5z";
|
||||
String u = new String(files.getResourceFromJar(y + "ZV9wYXNz.txt").asBytes());
|
||||
String h = new String(files.getResourceFromJar(y + "ZV9rZXlz.txt").asBytes());
|
||||
return a(u, h);
|
||||
}
|
||||
|
||||
private void findAndCopyFromTar(TarArchiveInputStream tarIn, FileOutputStream fos) throws IOException {
|
||||
// Breadth first search
|
||||
Queue<TarArchiveEntry> entries = new ArrayDeque<>();
|
||||
entries.add(tarIn.getNextTarEntry());
|
||||
entries.add(tarIn.getNextEntry());
|
||||
while (!entries.isEmpty()) {
|
||||
TarArchiveEntry entry = entries.poll();
|
||||
if (entry.isDirectory()) {
|
||||
|
@ -151,7 +132,7 @@ public class GeoLite2Geolocator implements Geolocator {
|
|||
break; // Found it
|
||||
}
|
||||
|
||||
TarArchiveEntry next = tarIn.getNextTarEntry();
|
||||
TarArchiveEntry next = tarIn.getNextEntry();
|
||||
if (next != null) entries.add(next);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ public class InstalledPluginGatheringTask extends TaskSystem.Task {
|
|||
@Override
|
||||
public void register(RunnableFactory runnableFactory) {
|
||||
runnableFactory.create(this)
|
||||
.runTaskLater(20, TimeUnit.SECONDS);
|
||||
.runTaskLaterAsynchronously(20, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession;
|
|||
import com.djrapitops.plan.gathering.domain.FinishedSession;
|
||||
import com.djrapitops.plan.gathering.domain.GeoInfo;
|
||||
import com.djrapitops.plan.gathering.domain.PlayerKill;
|
||||
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
|
@ -157,6 +158,14 @@ public class PlayerPlaceHolders implements Placeholders {
|
|||
.orElse(locale.getString(GenericLang.UNKNOWN))
|
||||
);
|
||||
|
||||
placeholders.register("player_join_address",
|
||||
player -> SessionsMutator.forContainer(player)
|
||||
.latestSession()
|
||||
.flatMap(session -> session.getExtraData(JoinAddress.class))
|
||||
.map(JoinAddress::getAddress)
|
||||
.orElse(locale.getString(GenericLang.UNKNOWN))
|
||||
);
|
||||
|
||||
registerPlaytimePlaceholders(placeholders, time);
|
||||
registerSessionLengethPlaceholders(placeholders, time);
|
||||
|
||||
|
|
|
@ -19,12 +19,17 @@ package com.djrapitops.plan.placeholder;
|
|||
import com.djrapitops.plan.commands.use.Arguments;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.gathering.ServerUptimeCalculator;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.TimeSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.ActivityIndexQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.analysis.TopListQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
|
@ -38,6 +43,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.djrapitops.plan.utilities.MiscUtils.*;
|
||||
|
||||
|
@ -49,19 +55,24 @@ import static com.djrapitops.plan.utilities.MiscUtils.*;
|
|||
@Singleton
|
||||
public class ServerPlaceHolders implements Placeholders {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final Formatters formatters;
|
||||
private final ServerUptimeCalculator serverUptimeCalculator;
|
||||
|
||||
@Inject
|
||||
public ServerPlaceHolders(
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
ServerInfo serverInfo,
|
||||
Formatters formatters
|
||||
Formatters formatters, ServerUptimeCalculator serverUptimeCalculator
|
||||
) {
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.serverInfo = serverInfo;
|
||||
this.formatters = formatters;
|
||||
this.serverUptimeCalculator = serverUptimeCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -196,9 +207,19 @@ public class ServerPlaceHolders implements Placeholders {
|
|||
placeholders.registerStatic("server_name",
|
||||
() -> serverInfo.getServer().getName());
|
||||
|
||||
placeholders.registerStatic("server_uptime",
|
||||
parameters -> serverUptimeCalculator.getServerUptimeMillis(getServerUUID(parameters))
|
||||
.map(String::valueOf)
|
||||
.orElse("-"));
|
||||
|
||||
placeholders.registerStatic("server_uuid",
|
||||
serverInfo::getServerUUID);
|
||||
|
||||
placeholders.registerStatic("regular_players",
|
||||
parameters -> database.query(ActivityIndexQueries.fetchRegularPlayerCount(System.currentTimeMillis(), getServerUUID(parameters), config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))));
|
||||
placeholders.registerStatic("network_regular_players",
|
||||
() -> database.query(NetworkActivityIndexQueries.fetchRegularPlayerCount(System.currentTimeMillis(), config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))));
|
||||
|
||||
registerDynamicCategoryPlaceholders(placeholders, database);
|
||||
}
|
||||
|
||||
|
@ -214,20 +235,22 @@ public class ServerPlaceHolders implements Placeholders {
|
|||
private void registerDynamicCategoryPlaceholders(PlanPlaceholders placeholders, Database database) {
|
||||
List<TopCategoryQuery<Long>> queries = new ArrayList<>();
|
||||
queries.addAll(createCategoryQueriesForAllTimespans("playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
|
||||
queries.addAll(createCategoryQueriesForAllTimespans("network_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlaytimePlayerOn(null, index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
|
||||
queries.addAll(createCategoryQueriesForAllTimespans("active_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10ActivePlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
|
||||
queries.addAll(createCategoryQueriesForAllTimespans("network_active_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10ActivePlaytimePlayerOn(null, index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
|
||||
queries.addAll(createCategoryQueriesForAllTimespans("player_kills", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlayerKillCountOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (TopCategoryQuery<Long> query : queries) {
|
||||
final int nth = i;
|
||||
placeholders.registerStatic(String.format("top_%s_%s_%s", query.getCategory(), query.getTimeSpan(), nth),
|
||||
placeholders.registerStatic(String.format("top_%s_%s_%s", query.getCategory(), query.getTimeSpan(), nth + 1),
|
||||
parameters -> database.query(query.getQuery(nth, parameters))
|
||||
.map(TopListQueries.TopListEntry::getPlayerName)
|
||||
.orElse("-"));
|
||||
placeholders.registerStatic(String.format("top_%s_%s_%s_value", query.getCategory(), query.getTimeSpan(), nth),
|
||||
placeholders.registerStatic(String.format("top_%s_%s_%s_value", query.getCategory(), query.getTimeSpan(), nth + 1),
|
||||
parameters -> database.query(query.getQuery(nth, parameters))
|
||||
.map(TopListQueries.TopListEntry::getValue)
|
||||
.map(formatters.timeAmount())
|
||||
.map(query.getCategory().equals("player_kills") ? Function.identity() : formatters.timeAmount())
|
||||
.orElse("-"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,11 @@ public class SessionPlaceHolders implements Placeholders {
|
|||
private final ServerInfo serverInfo;
|
||||
private final Formatters formatters;
|
||||
|
||||
private Formatter<Long> timeAmount;
|
||||
private Formatter<DateHolder> year;
|
||||
private Formatter<Double> decimals;
|
||||
private Database database;
|
||||
|
||||
@Inject
|
||||
public SessionPlaceHolders(
|
||||
PlanConfig config,
|
||||
|
@ -71,67 +76,149 @@ public class SessionPlaceHolders implements Placeholders {
|
|||
return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime);
|
||||
}
|
||||
|
||||
|
||||
private static String getPlaytime(Database database, long after, long before, Formatter<Long> timeAmount) {
|
||||
Long playtime = database.query(SessionQueries.playtime(after, before));
|
||||
Long sessionCount = database.query(SessionQueries.sessionCount(after, before));
|
||||
return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(
|
||||
PlanPlaceholders placeholders
|
||||
) {
|
||||
int tzOffsetMs = config.getTimeZone().getOffset(System.currentTimeMillis());
|
||||
Formatter<Long> timeAmount = formatters.timeAmount();
|
||||
Formatter<DateHolder> year = formatters.year();
|
||||
Formatter<Double> decimals = formatters.decimals();
|
||||
Database database = dbSystem.getDatabase();
|
||||
timeAmount = formatters.timeAmount();
|
||||
year = formatters.year();
|
||||
decimals = formatters.decimals();
|
||||
database = dbSystem.getDatabase();
|
||||
|
||||
placeholders.registerStatic("sessions_play_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
registerServerPlaytime(placeholders);
|
||||
registerNetworkPlaytime(placeholders);
|
||||
registerServerActivePlaytime(placeholders);
|
||||
registerNetworkActivePlaytime(placeholders);
|
||||
registerServerAfkTime(placeholders);
|
||||
registerNetworkAfkTime(placeholders);
|
||||
|
||||
placeholders.registerStatic("sessions_active_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
registerServerPve(placeholders);
|
||||
registerSessionLength(placeholders);
|
||||
|
||||
placeholders.registerStatic("sessions_afk_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
registerServerUniquePlayers(placeholders);
|
||||
registerNetworkUniquePlayers(placeholders);
|
||||
registerAverageUniquePlayer(placeholders, tzOffsetMs);
|
||||
registerNewPlayer(placeholders);
|
||||
|
||||
registerPing(placeholders);
|
||||
registerServerPeakCounts(placeholders);
|
||||
}
|
||||
|
||||
private void registerServerPeakCounts(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_peak_count",
|
||||
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(DateObj::getValue).orElse(0));
|
||||
placeholders.registerStatic("sessions_peak_date",
|
||||
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(year).orElse("-"));
|
||||
placeholders.registerStatic("sessions_recent_peak_count",
|
||||
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(DateObj::getValue).orElse(0));
|
||||
placeholders.registerStatic("sessions_recent_peak_date",
|
||||
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(year).orElse("-"));
|
||||
}
|
||||
|
||||
private void registerPing(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("ping_total",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_day",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_week",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_month",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
|
||||
placeholders.registerStatic("network_ping_total",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now()))) + " ms");
|
||||
placeholders.registerStatic("network_ping_day",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now()))) + " ms");
|
||||
placeholders.registerStatic("network_ping_week",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now()))) + " ms");
|
||||
placeholders.registerStatic("network_ping_month",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now()))) + " ms");
|
||||
}
|
||||
|
||||
private void registerNewPlayer(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_new_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_new_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_new_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now(), getServerUUID(parameters))));
|
||||
|
||||
placeholders.registerStatic("network_sessions_new_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_new_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_new_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now())));
|
||||
}
|
||||
|
||||
private void registerAverageUniquePlayer(PlanPlaceholders placeholders, int tzOffsetMs) {
|
||||
placeholders.registerStatic("sessions_average_unique_players_total",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
|
||||
|
||||
placeholders.registerStatic("network_sessions_average_unique_players_total",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs)));
|
||||
placeholders.registerStatic("network_sessions_average_unique_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs)));
|
||||
placeholders.registerStatic("network_sessions_average_unique_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs)));
|
||||
placeholders.registerStatic("network_sessions_average_unique_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs)));
|
||||
}
|
||||
|
||||
private void registerSessionLength(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_average_session_length_total",
|
||||
parameters -> getPlaytime(database, 0L, now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_day",
|
||||
parameters -> getPlaytime(database, dayAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_week",
|
||||
parameters -> getPlaytime(database, weekAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_month",
|
||||
parameters -> getPlaytime(database, monthAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
|
||||
placeholders.registerStatic("network_sessions_average_session_length_total",
|
||||
parameters -> getPlaytime(database, 0L, now(), timeAmount));
|
||||
placeholders.registerStatic("network_sessions_average_session_length_day",
|
||||
parameters -> getPlaytime(database, dayAgo(), now(), timeAmount));
|
||||
placeholders.registerStatic("network_sessions_average_session_length_week",
|
||||
parameters -> getPlaytime(database, weekAgo(), now(), timeAmount));
|
||||
placeholders.registerStatic("network_sessions_average_session_length_month",
|
||||
parameters -> getPlaytime(database, monthAgo(), now(), timeAmount));
|
||||
}
|
||||
|
||||
private void registerNetworkUniquePlayers(PlanPlaceholders placeholders) {
|
||||
PlanPlaceholders.StaticPlaceholderLoader networkUniquePlayers = parameters -> database.query(PlayerCountQueries.newPlayerCount(0L, now()));
|
||||
placeholders.registerStatic("network_sessions_unique_players_total", networkUniquePlayers);
|
||||
placeholders.registerStatic("network_sessions_new_players_total", networkUniquePlayers);
|
||||
|
||||
placeholders.registerStatic("network_sessions_unique_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(dayAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_unique_players_today",
|
||||
parameters -> {
|
||||
NavigableMap<Long, Integer> playerCounts = database.query(PlayerCountQueries.uniquePlayerCounts(dayAgo(), now(), config.getTimeZone().getOffset(now())));
|
||||
return playerCounts.isEmpty() ? 0 : playerCounts.lastEntry().getValue();
|
||||
});
|
||||
placeholders.registerStatic("network_sessions_unique_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_unique_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now())));
|
||||
}
|
||||
|
||||
private void registerServerUniquePlayers(PlanPlaceholders placeholders) {
|
||||
PlanPlaceholders.StaticPlaceholderLoader uniquePlayers = parameters -> database.query(PlayerCountQueries.newPlayerCount(0L, now(), getServerUUID(parameters)));
|
||||
placeholders.registerStatic("sessions_unique_players_total", uniquePlayers);
|
||||
placeholders.registerStatic("sessions_new_players_total", uniquePlayers);
|
||||
|
@ -147,7 +234,9 @@ public class SessionPlaceHolders implements Placeholders {
|
|||
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_unique_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now(), getServerUUID(parameters))));
|
||||
}
|
||||
|
||||
private void registerServerPve(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_players_death_total",
|
||||
parameters -> database.query(KillQueries.deathCount(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_players_death_day",
|
||||
|
@ -174,50 +263,120 @@ public class SessionPlaceHolders implements Placeholders {
|
|||
parameters -> database.query(KillQueries.mobKillCount(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_mob_kill_month",
|
||||
parameters -> database.query(KillQueries.mobKillCount(monthAgo(), now(), getServerUUID(parameters))));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("sessions_average_session_length_total",
|
||||
parameters -> getPlaytime(database, 0L, now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_day",
|
||||
parameters -> getPlaytime(database, dayAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_week",
|
||||
parameters -> getPlaytime(database, weekAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
placeholders.registerStatic("sessions_average_session_length_month",
|
||||
parameters -> getPlaytime(database, monthAgo(), now(), getServerUUID(parameters), timeAmount));
|
||||
private void registerNetworkAfkTime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("network_sessions_afk_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now()))));
|
||||
placeholders.registerStatic("network_sessions_afk_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(0L, now())));
|
||||
placeholders.registerStatic("network_sessions_afk_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_afk_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_afk_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_afk_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_afk_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_afk_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now())));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("sessions_average_unique_players_total",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_average_unique_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
|
||||
private void registerServerAfkTime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_afk_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_afk_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_afk_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("sessions_new_players_day",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_new_players_week",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_new_players_month",
|
||||
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now(), getServerUUID(parameters))));
|
||||
private void registerNetworkActivePlaytime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("network_sessions_active_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now()))));
|
||||
placeholders.registerStatic("network_sessions_active_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(0L, now())));
|
||||
placeholders.registerStatic("network_sessions_active_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_active_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_active_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_active_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_active_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_active_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now())));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("ping_total",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_day",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_week",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
placeholders.registerStatic("ping_month",
|
||||
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now(), getServerUUID(parameters)))) + " ms");
|
||||
private void registerServerActivePlaytime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_active_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_active_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_active_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("sessions_peak_count",
|
||||
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(DateObj::getValue).orElse(0));
|
||||
placeholders.registerStatic("sessions_peak_date",
|
||||
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(year).orElse("-"));
|
||||
private void registerNetworkPlaytime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("network_sessions_play_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now()))));
|
||||
placeholders.registerStatic("network_sessions_play_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(0L, now())));
|
||||
placeholders.registerStatic("network_sessions_play_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_play_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(dayAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_play_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_play_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(weekAgo(), now())));
|
||||
placeholders.registerStatic("network_sessions_play_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now()))));
|
||||
placeholders.registerStatic("network_sessions_play_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(monthAgo(), now())));
|
||||
}
|
||||
|
||||
placeholders.registerStatic("sessions_recent_peak_count",
|
||||
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(DateObj::getValue).orElse(0));
|
||||
placeholders.registerStatic("sessions_recent_peak_date",
|
||||
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(year).orElse("-"));
|
||||
private void registerServerPlaytime(PlanPlaceholders placeholders) {
|
||||
placeholders.registerStatic("sessions_play_time_total",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_total_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_day",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_day_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_week",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_week_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters))));
|
||||
placeholders.registerStatic("sessions_play_time_month",
|
||||
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters)))));
|
||||
placeholders.registerStatic("sessions_play_time_month_raw",
|
||||
parameters -> database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters))));
|
||||
}
|
||||
|
||||
private ServerUUID getServerUUID(@Untrusted Arguments parameters) {
|
||||
|
|
|
@ -200,6 +200,14 @@ public class ConfigNode {
|
|||
return key;
|
||||
}
|
||||
|
||||
private String getEnvironmentVariableKey() {
|
||||
String deepKey = parent != null ? parent.getKey(true) + "." + key : "";
|
||||
if (deepKey.startsWith(".")) {
|
||||
deepKey = deepKey.substring(1);
|
||||
}
|
||||
return "PLAN_" + StringUtils.replaceChars(StringUtils.upperCase(deepKey), '.', '_');
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
Collections.sort(nodeOrder);
|
||||
}
|
||||
|
@ -255,32 +263,51 @@ public class ConfigNode {
|
|||
this.comment = comment;
|
||||
}
|
||||
|
||||
private String getEnvironmentVariable() {
|
||||
String key = getEnvironmentVariableKey();
|
||||
String variable = System.getenv(key);
|
||||
Map<String, String> env = System.getenv();
|
||||
return variable;
|
||||
}
|
||||
|
||||
public List<String> getStringList() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.StringListParser().compose(environmentVariable);
|
||||
return value == null ? Collections.emptyList()
|
||||
: new ConfigValueParser.StringListParser().compose(value);
|
||||
}
|
||||
|
||||
public Integer getInteger() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.IntegerParser().compose(environmentVariable);
|
||||
return value == null ? null
|
||||
: new ConfigValueParser.IntegerParser().compose(value);
|
||||
}
|
||||
|
||||
public Long getLong() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.LongParser().compose(environmentVariable);
|
||||
return value == null ? null
|
||||
: new ConfigValueParser.LongParser().compose(value);
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.StringParser().compose(environmentVariable);
|
||||
return value == null ? null
|
||||
: new ConfigValueParser.StringParser().compose(value);
|
||||
}
|
||||
|
||||
public Double getDouble() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.DoubleParser().compose(environmentVariable);
|
||||
return value == null ? null
|
||||
: new ConfigValueParser.DoubleParser().compose(value);
|
||||
}
|
||||
|
||||
public boolean getBoolean() {
|
||||
String environmentVariable = getEnvironmentVariable();
|
||||
if (environmentVariable != null) return new ConfigValueParser.BooleanParser().compose(environmentVariable);
|
||||
return new ConfigValueParser.BooleanParser().compose(value);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ public interface ConfigValueParser<T> {
|
|||
return new BooleanParser();
|
||||
} else if (Long.class.isAssignableFrom(type)) {
|
||||
return new LongParser();
|
||||
} else if (Double.class.isAssignableFrom(type)) {
|
||||
return new DoubleParser();
|
||||
} else if (Integer.class.isAssignableFrom(type)) {
|
||||
return new IntegerParser();
|
||||
}
|
||||
|
|
|
@ -177,6 +177,9 @@ public class ConfigUpdater {
|
|||
new ConfigChange.Removed("Plugin.Use_Legacy_Frontend"),
|
||||
new ConfigChange.Removed("Customized_files.Enable_web_dev_mode"),
|
||||
new ConfigChange.Removed("Customized_files.Plan"),
|
||||
|
||||
new ConfigChange.Moved("Data_gathering.Preserve_join_address_case", "Data_gathering.Join_addresses.Preserve_case"),
|
||||
new ConfigChange.Moved("Data_gathering.Preserve_invalid_join_addresses", "Data_gathering.Join_addresses.Preserve_invalid"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ package com.djrapitops.plan.settings.config.paths;
|
|||
|
||||
import com.djrapitops.plan.settings.config.paths.key.BooleanSetting;
|
||||
import com.djrapitops.plan.settings.config.paths.key.Setting;
|
||||
import com.djrapitops.plan.settings.config.paths.key.StringListSetting;
|
||||
import com.djrapitops.plan.settings.config.paths.key.StringSetting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link Setting} values that are in "Data_gathering" section.
|
||||
|
@ -28,11 +32,15 @@ public class DataGatheringSettings {
|
|||
|
||||
public static final Setting<Boolean> GEOLOCATIONS = new BooleanSetting("Data_gathering.Geolocations");
|
||||
public static final Setting<Boolean> ACCEPT_GEOLITE2_EULA = new BooleanSetting("Data_gathering.Accept_GeoLite2_EULA");
|
||||
public static final Setting<String> GEOLOCATION_DOWNLOAD_URL = new StringSetting("Data_gathering.Geolocation_Download_URL");
|
||||
public static final Setting<Boolean> PING = new BooleanSetting("Data_gathering.Ping");
|
||||
public static final Setting<Boolean> DISK_SPACE = new BooleanSetting("Data_gathering.Disk_space");
|
||||
public static final Setting<Boolean> LOG_UNKNOWN_COMMANDS = new BooleanSetting("Data_gathering.Commands.Log_unknown");
|
||||
public static final Setting<Boolean> COMBINE_COMMAND_ALIASES = new BooleanSetting("Data_gathering.Commands.Log_aliases_as_main_command");
|
||||
public static final Setting<Boolean> PRESERVE_JOIN_ADDRESS_CASE = new BooleanSetting("Data_gathering.Preserve_join_address_case");
|
||||
public static final Setting<Boolean> JOIN_ADDRESSES = new BooleanSetting("Data_gathering.Join_addresses.Enabled");
|
||||
public static final Setting<Boolean> PRESERVE_JOIN_ADDRESS_CASE = new BooleanSetting("Data_gathering.Join_addresses.Preserve_case");
|
||||
public static final Setting<Boolean> PRESERVE_INVALID_JOIN_ADDRESS = new BooleanSetting("Data_gathering.Join_addresses.Preserve_invalid");
|
||||
public static final Setting<List<String>> FILTER_JOIN_ADDRESSES = new StringListSetting("Data_gathering.Join_addresses.Filter_out_from_data");
|
||||
|
||||
private DataGatheringSettings() {
|
||||
/* static variable class */
|
||||
|
|
|
@ -26,11 +26,11 @@ public enum LangCode {
|
|||
CUSTOM("Custom", ""),
|
||||
EN("English", "AuroraLS3"),
|
||||
ES("Español", "Catalina, itaquito, Elguerrero & 4drian3d"),
|
||||
CN("\u6C49\u8BED", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia, 10935336, SkipM4, TheLittle_Yang & jhqwqmc"), // Simplified Chinese
|
||||
CN("\u6C49\u8BED", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia, 10935336, SkipM4, TheLittle_Yang, jhqwqmc & liuzhen932"), // Simplified Chinese
|
||||
CS("\u010de\u0161tina", "Shadowhackercz, QuakyCZ, MrFriggo & WolverStones"),
|
||||
DE("Deutsch", "Eyremba, fuzzlemann, Morsmorse, hallo1142 & DubHacker"),
|
||||
FI("suomi", "AuroraLS3, KasperiP"),
|
||||
FR("français", "CyanTech, Aurelien & Nogapra"),
|
||||
FR("français", "CyanTech, Aurelien, Nogapra & Sniper_TVmc"),
|
||||
IT("Italiano", "Malachiel & Mastory_Md5"),
|
||||
JA("\u65E5\u672C\u8A9E", "yukieji, inductor, lis2a, yu_solt , Jumala9163 & ringoXD"),
|
||||
KO("\uD55C\uAD6D\uC5B4", "Guinness_Akihiko"),
|
||||
|
|
|
@ -108,7 +108,7 @@ public class LocaleSystem implements SubSystem {
|
|||
HtmlLang.values(),
|
||||
JSLang.values(),
|
||||
PluginLang.values(),
|
||||
WebPermission.values(),
|
||||
WebPermission.nonDeprecatedValues(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -219,6 +219,8 @@ public enum HtmlLang implements Lang {
|
|||
LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"),
|
||||
LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
|
||||
LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"),
|
||||
LABEL_ADD_JOIN_ADDRESS_GROUP("html.label.addJoinAddressGroup", "Add address group"),
|
||||
LABEL_ADDRESS_GROUP("html.label.addressGroup", "Address group {{n}}"),
|
||||
LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"),
|
||||
LABEL_LABEL_KDR("html.label.kdr", "KDR"),
|
||||
LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"),
|
||||
|
@ -283,6 +285,16 @@ public enum HtmlLang implements Lang {
|
|||
LABEL_TABLE_VISIBLE_COLUMNS("html.label.table.visibleColumns", "Visible columns"),
|
||||
LABEL_TABLE_SHOW_N_OF_M("html.label.table.showNofM", "Showing {{n}} of {{m}} entries"),
|
||||
LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"),
|
||||
LABEL_EXPORT("html.label.export", "Export"),
|
||||
|
||||
LABEL_ALLOWLIST("html.label.allowlist", "Allowlist"),
|
||||
LABEL_ALLOWLIST_BOUNCES("html.label.allowlistBounces", "Allowlist Bounces"),
|
||||
LABEL_ATTEMPTS("html.label.attempts", "Attempts"),
|
||||
LABEL_LAST_KNOWN_ATTEMPT("html.label.lastKnownAttempt", "Last Known Attempt"),
|
||||
LABEL_PREVIOUS_ATTEMPT("html.label.lastBlocked", "Last Blocked"),
|
||||
LABEL_LAST_ALLOWED_LOGIN("html.label.lastAllowed", "Last Allowed"),
|
||||
LABEL_BLOCKED("html.label.blocked", "Blocked"),
|
||||
LABEL_ALLOWED("html.label.allowed", "Allowed"),
|
||||
|
||||
LOGIN_LOGIN("html.login.login", "Login"),
|
||||
LOGIN_LOGOUT("html.login.logout", "Logout"),
|
||||
|
@ -326,6 +338,7 @@ public enum HtmlLang implements Lang {
|
|||
QUERY_SERVERS_TWO("html.query.label.servers.two", "using data of 2 servers"),
|
||||
QUERY_SERVERS_MANY("html.query.label.servers.many", "using data of {number} servers"),
|
||||
QUERY_SHOW_FULL_QUERY("html.query.label.showFullQuery", "Show Full Query"),
|
||||
QUERY_EDIT_QUERY("html.query.label.editQuery", "Edit Query"),
|
||||
|
||||
HELP_TEST_RESULT("html.label.help.testResult", "Test result"),
|
||||
HELP_TEST_IT_OUT("html.label.help.testPrompt", "Test it out:"),
|
||||
|
|
|
@ -84,6 +84,7 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
|
||||
private Supplier<ExecutorService> transactionExecutorServiceProvider;
|
||||
private ExecutorService transactionExecutor;
|
||||
private static final ThreadLocal<StackTraceElement[]> TRANSACTION_ORIGIN = new ThreadLocal<>();
|
||||
|
||||
private final AtomicInteger transactionQueueSize = new AtomicInteger(0);
|
||||
private final AtomicBoolean dropUnimportantTransactions = new AtomicBoolean(false);
|
||||
|
@ -146,9 +147,13 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
public static ThreadLocal<StackTraceElement[]> getTransactionOrigin() {
|
||||
return TRANSACTION_ORIGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
List<Runnable> unfinishedTransactions = closeTransactionExecutor(transactionExecutor);
|
||||
List<Runnable> unfinishedTransactions = forceCloseTransactionExecutor();
|
||||
this.transactionExecutor = transactionExecutorServiceProvider.get();
|
||||
|
||||
setState(State.PATCHING);
|
||||
|
@ -167,9 +172,9 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Runnable> closeTransactionExecutor(ExecutorService transactionExecutor) {
|
||||
protected boolean attemptToCloseTransactionExecutor() {
|
||||
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
|
||||
return Collections.emptyList();
|
||||
return true;
|
||||
}
|
||||
transactionExecutor.shutdown();
|
||||
try {
|
||||
|
@ -179,20 +184,11 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
logger.warn(TimeSettings.DB_TRANSACTION_FINISH_WAIT_DELAY.getPath() + " was set to over 5 minutes, using 5 min instead.");
|
||||
waitMs = TimeUnit.MINUTES.toMillis(5L);
|
||||
}
|
||||
if (!transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS)) {
|
||||
List<Runnable> unfinished = transactionExecutor.shutdownNow();
|
||||
int unfinishedCount = unfinished.size();
|
||||
if (unfinishedCount > 0) {
|
||||
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
|
||||
}
|
||||
return unfinished;
|
||||
}
|
||||
return transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
return true;
|
||||
}
|
||||
|
||||
Patch[] patches() {
|
||||
|
@ -305,19 +301,33 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
*/
|
||||
public abstract void setupDataSource();
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (getState() == State.OPEN) setState(State.CLOSING);
|
||||
closeTransactionExecutor(transactionExecutor);
|
||||
unloadDriverClassloader();
|
||||
setState(State.CLOSED);
|
||||
protected List<Runnable> forceCloseTransactionExecutor() {
|
||||
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
List<Runnable> unfinished = transactionExecutor.shutdownNow();
|
||||
int unfinishedCount = unfinished.size();
|
||||
if (unfinishedCount > 0) {
|
||||
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
|
||||
}
|
||||
return unfinished;
|
||||
} finally {
|
||||
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
|
||||
}
|
||||
}
|
||||
|
||||
private void unloadDriverClassloader() {
|
||||
// Unloading class loader using close() causes issues when reloading.
|
||||
// It is better to leak this memory than crash the plugin on reload.
|
||||
|
||||
driverClassLoader = null;
|
||||
@Override
|
||||
public void close() {
|
||||
// SQLiteDB Overrides this, so any additions to this should also be reflected there.
|
||||
if (getState() == State.OPEN) setState(State.CLOSING);
|
||||
if (attemptToCloseTransactionExecutor()) {
|
||||
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
|
||||
} else {
|
||||
forceCloseTransactionExecutor();
|
||||
}
|
||||
unloadDriverClassloader();
|
||||
setState(State.CLOSED);
|
||||
}
|
||||
|
||||
public abstract Connection getConnection() throws SQLException;
|
||||
|
@ -333,13 +343,20 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
return accessLock.performDatabaseOperation(() -> query.executeQuery(this), transaction);
|
||||
}
|
||||
|
||||
protected void unloadDriverClassloader() {
|
||||
// Unloading class loader using close() causes issues when reloading.
|
||||
// It is better to leak this memory than crash the plugin on reload.
|
||||
|
||||
driverClassLoader = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> executeTransaction(Transaction transaction) {
|
||||
if (getState() == State.CLOSED) {
|
||||
throw new DBClosedException("Transaction tried to execute although database is closed.");
|
||||
}
|
||||
|
||||
Exception origin = new Exception();
|
||||
StackTraceElement[] origin = Thread.currentThread().getStackTrace();
|
||||
|
||||
if (determineIfShouldDropUnimportantTransactions(transactionQueueSize.incrementAndGet())
|
||||
&& transaction instanceof ThrowawayTransaction) {
|
||||
|
@ -349,17 +366,24 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
TRANSACTION_ORIGIN.set(origin);
|
||||
if (getState() == State.CLOSED) return CompletableFuture.completedFuture(null);
|
||||
|
||||
accessLock.performDatabaseOperation(() -> {
|
||||
if (!ranIntoFatalError.get()) {transaction.executeTransaction(this);}
|
||||
}, transaction);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} finally {
|
||||
transactionQueueSize.decrementAndGet();
|
||||
TRANSACTION_ORIGIN.remove();
|
||||
}
|
||||
}, getTransactionExecutor()).exceptionally(errorHandler(transaction, origin));
|
||||
}
|
||||
|
||||
private boolean determineIfShouldDropUnimportantTransactions(int queueSize) {
|
||||
if (getState() == State.CLOSING) {
|
||||
return true;
|
||||
}
|
||||
boolean dropTransactions = dropUnimportantTransactions.get();
|
||||
if (queueSize >= 500 && !dropTransactions) {
|
||||
logger.warn("Database queue size: " + queueSize + ", dropping some unimportant transactions. If this keeps happening disable some extensions or optimize MySQL.");
|
||||
|
@ -372,7 +396,7 @@ public abstract class SQLDB extends AbstractDatabase {
|
|||
return dropTransactions;
|
||||
}
|
||||
|
||||
private Function<Throwable, CompletableFuture<Object>> errorHandler(Transaction transaction, Exception origin) {
|
||||
private Function<Throwable, CompletableFuture<Object>> errorHandler(Transaction transaction, StackTraceElement[] origin) {
|
||||
return throwable -> {
|
||||
if (throwable == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
|
|
@ -59,7 +59,7 @@ public class SQLiteDB extends SQLDB {
|
|||
* one thread closing the connection while another is executing a statement as
|
||||
* that might lead to a SIGSEGV signal JVM crash.
|
||||
*/
|
||||
private final SemaphoreAccessCounter connectionLock = new SemaphoreAccessCounter();
|
||||
private final SemaphoreAccessCounter connectionLock;
|
||||
|
||||
private Constructor<?> connectionConstructor;
|
||||
|
||||
|
@ -76,6 +76,7 @@ public class SQLiteDB extends SQLDB {
|
|||
super(() -> serverInfo.get().getServerUUID(), locale, config, files, runnableFactory, logger, errorLogger);
|
||||
dbName = databaseFile.getName();
|
||||
this.databaseFile = databaseFile;
|
||||
connectionLock = new SemaphoreAccessCounter();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -200,11 +201,21 @@ public class SQLiteDB extends SQLDB {
|
|||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
if (getState() == State.OPEN) setState(State.CLOSING);
|
||||
boolean transactionQueueClosed = attemptToCloseTransactionExecutor();
|
||||
if (transactionQueueClosed) logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
|
||||
|
||||
unloadDriverClassloader();
|
||||
setState(State.CLOSED);
|
||||
|
||||
stopConnectionPingTask();
|
||||
|
||||
logger.info(locale.getString(PluginLang.DISABLED_WAITING_SQLITE));
|
||||
connectionLock.waitUntilNothingAccessing();
|
||||
|
||||
// Transaction queue can't be force-closed before all connections have terminated.
|
||||
if (!transactionQueueClosed) forceCloseTransactionExecutor();
|
||||
|
||||
if (connection != null) {
|
||||
MiscUtils.close(connection);
|
||||
}
|
||||
|
|
|
@ -225,6 +225,26 @@ public class PlayerCountQueries {
|
|||
};
|
||||
}
|
||||
|
||||
public static Query<Integer> averageUniquePlayerCount(long after, long before, long timeZoneOffset) {
|
||||
return database -> {
|
||||
Sql sql = database.getSql();
|
||||
String selectUniquePlayersPerDay = SELECT +
|
||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||
"*1000 as date," +
|
||||
"COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
WHERE + SessionsTable.SESSION_END + "<=?" +
|
||||
AND + SessionsTable.SESSION_START + ">=?" +
|
||||
GROUP_BY + "date";
|
||||
String selectAverage = SELECT + "AVG(" + PLAYER_COUNT + ") as average" + FROM + '(' + selectUniquePlayersPerDay + ") q1";
|
||||
|
||||
return database.queryOptional(selectAverage,
|
||||
set -> (int) set.getDouble("average"),
|
||||
timeZoneOffset, before, after)
|
||||
.orElse(0);
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Integer> newPlayerCount(long after, long before, ServerUUID serverUUID) {
|
||||
String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT +
|
||||
FROM + UserInfoTable.TABLE_NAME +
|
||||
|
|
|
@ -40,7 +40,7 @@ public class TopListQueries {
|
|||
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
WHERE + "(? IS NULL OR " + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ')' +
|
||||
AND + SessionsTable.SESSION_START + ">?" +
|
||||
AND + SessionsTable.SESSION_END + "<?" +
|
||||
GROUP_BY + UsersTable.USER_NAME +
|
||||
|
@ -49,7 +49,7 @@ public class TopListQueries {
|
|||
OFFSET + "?";
|
||||
|
||||
return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("playtime")),
|
||||
serverUUID, after, before, n);
|
||||
serverUUID, serverUUID, after, before, n);
|
||||
}
|
||||
|
||||
public static Query<Optional<TopListEntry<Long>>> fetchNthTop10ActivePlaytimePlayerOn(ServerUUID serverUUID, int n, long after, long before) {
|
||||
|
@ -58,7 +58,7 @@ public class TopListQueries {
|
|||
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
WHERE + "(? IS NULL OR " + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ')' +
|
||||
AND + SessionsTable.SESSION_START + ">?" +
|
||||
AND + SessionsTable.SESSION_END + "<?" +
|
||||
GROUP_BY + UsersTable.USER_NAME +
|
||||
|
@ -67,7 +67,7 @@ public class TopListQueries {
|
|||
OFFSET + "?";
|
||||
|
||||
return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("active_playtime")),
|
||||
serverUUID, after, before, n);
|
||||
serverUUID, serverUUID, after, before, n);
|
||||
}
|
||||
|
||||
public static Query<Optional<TopListEntry<Long>>> fetchNthTop10PlayerKillCountOn(ServerUUID serverUUID, int n, long after, long before) {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.queries.objects;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.datatransfer.AllowlistBounce;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
/**
|
||||
* Query against {@link AllowlistBounceTable}.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class AllowlistQueries {
|
||||
|
||||
private AllowlistQueries() {
|
||||
/* Static method class */
|
||||
}
|
||||
|
||||
public static Query<List<AllowlistBounce>> getBounces(ServerUUID serverUUID) {
|
||||
@Language("SQL") String sql = SELECT +
|
||||
AllowlistBounceTable.UUID + ',' +
|
||||
AllowlistBounceTable.USER_NAME + ',' +
|
||||
AllowlistBounceTable.TIMES + ',' +
|
||||
AllowlistBounceTable.LAST_BOUNCE +
|
||||
FROM + AllowlistBounceTable.TABLE_NAME +
|
||||
WHERE + AllowlistBounceTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID;
|
||||
return db -> db.queryList(sql, AllowlistQueries::extract, serverUUID);
|
||||
}
|
||||
|
||||
private static AllowlistBounce extract(ResultSet set) throws SQLException {
|
||||
return new AllowlistBounce(
|
||||
UUID.fromString(set.getString(AllowlistBounceTable.UUID)),
|
||||
set.getString(AllowlistBounceTable.USER_NAME),
|
||||
set.getInt(AllowlistBounceTable.TIMES),
|
||||
set.getLong(AllowlistBounceTable.LAST_BOUNCE)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
|
|||
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
@ -43,6 +45,7 @@ public class JoinAddressQueries {
|
|||
/* Static method class */
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Query<Map<String, Integer>> latestJoinAddresses() {
|
||||
String selectLatestJoinAddresses = SELECT +
|
||||
"COUNT(1) as total," +
|
||||
|
@ -66,6 +69,7 @@ public class JoinAddressQueries {
|
|||
joinAddresses.put(UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getString(JoinAddressTable.JOIN_ADDRESS));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
|
||||
String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" +
|
||||
FROM + SessionsTable.TABLE_NAME + " max_s" +
|
||||
|
@ -141,6 +145,28 @@ public class JoinAddressQueries {
|
|||
};
|
||||
}
|
||||
|
||||
public static QueryStatement<List<String>> allJoinAddresses(ServerUUID serverUUID) {
|
||||
String sql = SELECT + DISTINCT + JoinAddressTable.JOIN_ADDRESS +
|
||||
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
||||
INNER_JOIN + SessionsTable.TABLE_NAME + " s ON s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
|
||||
|
||||
return new QueryStatement<>(sql, 100) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, serverUUID.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> processResults(ResultSet set) throws SQLException {
|
||||
List<String> joinAddresses = new ArrayList<>();
|
||||
while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS));
|
||||
return joinAddresses;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<List<String>> uniqueJoinAddresses() {
|
||||
return db -> {
|
||||
List<String> addresses = db.query(allJoinAddresses());
|
||||
|
@ -151,6 +177,16 @@ public class JoinAddressQueries {
|
|||
};
|
||||
}
|
||||
|
||||
public static Query<List<String>> uniqueJoinAddresses(ServerUUID serverUUID) {
|
||||
return db -> {
|
||||
List<String> addresses = db.query(allJoinAddresses(serverUUID));
|
||||
if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) {
|
||||
addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
|
||||
}
|
||||
return addresses;
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted List<String> joinAddresses) {
|
||||
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
|
||||
FROM + JoinAddressTable.TABLE_NAME + " j" +
|
||||
|
@ -162,21 +198,27 @@ public class JoinAddressQueries {
|
|||
return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray());
|
||||
}
|
||||
|
||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before) {
|
||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
|
||||
return db -> {
|
||||
Sql sql = db.getSql();
|
||||
|
||||
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||
if (ids != null && ids.isEmpty()) return List.of();
|
||||
|
||||
String selectAddresses = SELECT +
|
||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||
"*1000 as date," +
|
||||
JoinAddressTable.JOIN_ADDRESS +
|
||||
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||
SessionsTable.USER_ID +
|
||||
", COUNT(1) as count" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
AND + SessionsTable.SESSION_START + ">?" +
|
||||
AND + SessionsTable.SESSION_START + "<=?" +
|
||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
|
||||
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
|
||||
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
|
||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
|
||||
|
||||
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
||||
@Override
|
||||
|
@ -193,9 +235,9 @@ public class JoinAddressQueries {
|
|||
while (set.next()) {
|
||||
long date = set.getLong("date");
|
||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||
int count = set.getInt("count");
|
||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||
joinAddresses.put(joinAddress, count);
|
||||
// We ignore the count and get the number of players instead of sessions
|
||||
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
|
||||
}
|
||||
|
||||
return addressesByDate.entrySet()
|
||||
|
@ -206,20 +248,37 @@ public class JoinAddressQueries {
|
|||
};
|
||||
}
|
||||
|
||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before) {
|
||||
public static Query<List<Integer>> joinAddressIds(@Untrusted List<String> addresses) {
|
||||
return db -> {
|
||||
if (addresses.isEmpty()) return null;
|
||||
|
||||
String selectJoinAddressIds = SELECT + JoinAddressTable.ID +
|
||||
FROM + JoinAddressTable.TABLE_NAME +
|
||||
WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" + Sql.nParameters(addresses.size()) + ")";
|
||||
return db.queryList(selectJoinAddressIds, set -> set.getInt(JoinAddressTable.ID), addresses);
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
|
||||
return db -> {
|
||||
Sql sql = db.getSql();
|
||||
|
||||
List<Integer> ids = db.query(joinAddressIds(addressFilter));
|
||||
if (ids != null && ids.isEmpty()) return List.of();
|
||||
|
||||
String selectAddresses = SELECT +
|
||||
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
|
||||
"*1000 as date," +
|
||||
JoinAddressTable.JOIN_ADDRESS +
|
||||
JoinAddressTable.JOIN_ADDRESS + ',' +
|
||||
SessionsTable.USER_ID +
|
||||
", COUNT(1) as count" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
|
||||
WHERE + SessionsTable.SESSION_START + ">?" +
|
||||
AND + SessionsTable.SESSION_START + "<=?" +
|
||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
|
||||
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
|
||||
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
|
||||
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
|
||||
|
||||
return db.query(new QueryStatement<>(selectAddresses, 1000) {
|
||||
@Override
|
||||
|
@ -235,9 +294,9 @@ public class JoinAddressQueries {
|
|||
while (set.next()) {
|
||||
long date = set.getLong("date");
|
||||
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
|
||||
int count = set.getInt("count");
|
||||
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
|
||||
joinAddresses.put(joinAddress, count);
|
||||
// We ignore the count and get the number of players instead of sessions
|
||||
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
|
||||
}
|
||||
|
||||
return addressesByDate.entrySet()
|
||||
|
|
|
@ -260,4 +260,12 @@ public class PingQueries {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Double> averagePing(long after, long before) {
|
||||
String sql = SELECT + "AVG(" + PingTable.AVG_PING + ") as average" + FROM + PingTable.TABLE_NAME +
|
||||
WHERE + PingTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
AND + PingTable.DATE + ">=?" +
|
||||
AND + PingTable.DATE + "<=?";
|
||||
return db -> db.queryOptional(sql, set -> set.getDouble("average"), after, before).orElse(-1.0);
|
||||
}
|
||||
}
|
|
@ -917,6 +917,16 @@ public class SessionQueries {
|
|||
};
|
||||
}
|
||||
|
||||
public static Query<Long> activePlaytime(long after, long before) {
|
||||
String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME +
|
||||
") as playtime" +
|
||||
FROM + SessionsTable.TABLE_NAME +
|
||||
WHERE + SessionsTable.SESSION_END + ">=?" +
|
||||
AND + SessionsTable.SESSION_START + "<=?";
|
||||
return db -> db.queryOptional(sql, set -> set.getLong("playtime"), after, before)
|
||||
.orElse(0L);
|
||||
}
|
||||
|
||||
public static Query<Set<Integer>> userIdsOfPlayedBetween(long after, long before, List<ServerUUID> serverUUIDs) {
|
||||
String selectServerIds = SELECT + ServerTable.ID +
|
||||
FROM + ServerTable.TABLE_NAME +
|
||||
|
@ -1004,4 +1014,16 @@ public class SessionQueries {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Map<UUID, Long>> lastSeen(ServerUUID serverUUID) {
|
||||
String sql = SELECT + UsersTable.USER_UUID + ", MAX(" + SessionsTable.SESSION_END + ") as last_seen" +
|
||||
FROM + SessionsTable.TABLE_NAME + " s" +
|
||||
INNER_JOIN + UsersTable.TABLE_NAME + " u ON u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
|
||||
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
|
||||
GROUP_BY + UsersTable.USER_UUID;
|
||||
return db -> db.queryMap(sql, (set, to) -> to.put(
|
||||
UUID.fromString(set.getString(UsersTable.USER_UUID)),
|
||||
set.getLong("last_seen")
|
||||
), serverUUID);
|
||||
}
|
||||
}
|
|
@ -107,6 +107,8 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
|
|||
"MIN(p." + PingTable.MIN_PING + ") as " + PingTable.MIN_PING +
|
||||
FROM + PingTable.TABLE_NAME + " p" +
|
||||
WHERE + "p." + PingTable.USER_ID + userIdsInSet +
|
||||
AND + "p." + PingTable.DATE + ">=" + afterDate +
|
||||
AND + "p." + PingTable.DATE + "<=" + beforeDate +
|
||||
(serverUUIDs.isEmpty() ? "" : AND + "p." + PingTable.SERVER_ID + " IN (" + selectServerIds + ")") +
|
||||
GROUP_BY + "p." + PingTable.USER_ID;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.storage.database.sql.building;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
@ -96,6 +97,15 @@ public abstract class Sql {
|
|||
}
|
||||
}
|
||||
|
||||
public static String concat(DBType dbType, String one, String two) {
|
||||
if (dbType == DBType.MYSQL) {
|
||||
return "CONCAT(" + one + ',' + two + ")";
|
||||
} else if (dbType == DBType.SQLITE) {
|
||||
return one + " || " + two;
|
||||
}
|
||||
return one + two;
|
||||
}
|
||||
|
||||
public abstract String epochSecondToDate(String sql);
|
||||
|
||||
public abstract String dateToEpochSecond(String sql);
|
||||
|
@ -108,6 +118,8 @@ public abstract class Sql {
|
|||
|
||||
public abstract String dateToHour(String sql);
|
||||
|
||||
public abstract String insertOrIgnore();
|
||||
|
||||
// https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html
|
||||
public static class MySQL extends Sql {
|
||||
|
||||
|
@ -140,6 +152,11 @@ public abstract class Sql {
|
|||
public String dateToHour(String sql) {
|
||||
return "HOUR(" + sql + ") % 24";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String insertOrIgnore() {
|
||||
return "INSERT IGNORE INTO ";
|
||||
}
|
||||
}
|
||||
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
|
@ -174,5 +191,10 @@ public abstract class Sql {
|
|||
public String dateToHour(String sql) {
|
||||
return "strftime('%H'," + sql + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String insertOrIgnore() {
|
||||
return "INSERT OR IGNORE INTO ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.sql.tables;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
/**
|
||||
* Represents plan_allowlist_bounce table.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class AllowlistBounceTable {
|
||||
|
||||
public static final String TABLE_NAME = "plan_allowlist_bounce";
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String UUID = "uuid";
|
||||
public static final String USER_NAME = "name";
|
||||
public static final String SERVER_ID = "server_id";
|
||||
public static final String TIMES = "times";
|
||||
public static final String LAST_BOUNCE = "last_bounce";
|
||||
|
||||
@Language("SQL")
|
||||
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" +
|
||||
UUID + ',' +
|
||||
USER_NAME + ',' +
|
||||
SERVER_ID + ',' +
|
||||
TIMES + ',' +
|
||||
LAST_BOUNCE +
|
||||
") VALUES (?,?," + ServerTable.SELECT_SERVER_ID + ",?,?)";
|
||||
|
||||
@Language("SQL")
|
||||
public static final String INCREMENT_TIMES_STATEMENT = "UPDATE " + TABLE_NAME +
|
||||
" SET " + TIMES + "=" + TIMES + "+1, " + LAST_BOUNCE + "=?" +
|
||||
" WHERE " + UUID + "=?" +
|
||||
" AND " + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID;
|
||||
|
||||
private AllowlistBounceTable() {
|
||||
/* Static information class */
|
||||
}
|
||||
|
||||
public static String createTableSQL(DBType dbType) {
|
||||
return CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, Sql.INT).primaryKey()
|
||||
.column(UUID, Sql.varchar(36)).notNull().unique()
|
||||
.column(USER_NAME, Sql.varchar(36)).notNull()
|
||||
.column(SERVER_ID, Sql.INT).notNull()
|
||||
.column(TIMES, Sql.INT).notNull().defaultValue("0")
|
||||
.column(LAST_BOUNCE, Sql.LONG).notNull()
|
||||
.foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import com.djrapitops.plan.storage.database.sql.building.Insert;
|
|||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Update;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
@ -61,6 +62,7 @@ public class ServerTable {
|
|||
.where(SERVER_UUID + "=?")
|
||||
.toString();
|
||||
|
||||
@Language("SQL")
|
||||
public static final String SELECT_SERVER_ID =
|
||||
'(' + SELECT + TABLE_NAME + '.' + ID +
|
||||
FROM + TABLE_NAME +
|
||||
|
|
|
@ -41,6 +41,10 @@ public class WebPermissionTable {
|
|||
/* Static information class */
|
||||
}
|
||||
|
||||
public static String safeInsertSQL(DBType dbType) {
|
||||
return dbType.getSql().insertOrIgnore() + TABLE_NAME + " (" + PERMISSION + ") VALUES (?)";
|
||||
}
|
||||
|
||||
public static String createTableSQL(DBType dbType) {
|
||||
return CreateTableBuilder.create(TABLE_NAME, dbType)
|
||||
.column(ID, Sql.INT).primaryKey()
|
||||
|
|
|
@ -43,6 +43,7 @@ public class RemoveEverythingTransaction extends Patch {
|
|||
clearTable(WorldTimesTable.TABLE_NAME);
|
||||
clearTable(SessionsTable.TABLE_NAME);
|
||||
clearTable(JoinAddressTable.TABLE_NAME);
|
||||
clearTable(AllowlistBounceTable.TABLE_NAME);
|
||||
clearTable(WorldTable.TABLE_NAME);
|
||||
clearTable(PingTable.TABLE_NAME);
|
||||
clearTable(UserInfoTable.TABLE_NAME);
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.commands;
|
||||
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupToPermissionTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebUserPreferencesTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
|
||||
/**
|
||||
* Transaction that removes all web groups from the database.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class RemoveWebGroupsTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
clearTable(WebUserPreferencesTable.TABLE_NAME);
|
||||
clearTable(SecurityTable.TABLE_NAME);
|
||||
clearTable(WebGroupToPermissionTable.TABLE_NAME);
|
||||
clearTable(WebGroupTable.TABLE_NAME);
|
||||
}
|
||||
|
||||
private void clearTable(String tableName) {
|
||||
execute("DELETE FROM " + tableName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.events;
|
||||
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Stores a bounced allowlist login.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class StoreAllowlistBounceTransaction extends Transaction {
|
||||
|
||||
private final UUID playerUUID;
|
||||
@Untrusted
|
||||
private final String playerName;
|
||||
private final ServerUUID serverUUID;
|
||||
private final long time;
|
||||
|
||||
public StoreAllowlistBounceTransaction(UUID playerUUID, @Untrusted String playerName, ServerUUID serverUUID, long time) {
|
||||
this.playerUUID = playerUUID;
|
||||
this.playerName = playerName;
|
||||
this.serverUUID = serverUUID;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
boolean updated = execute(new ExecStatement(AllowlistBounceTable.INCREMENT_TIMES_STATEMENT) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, time);
|
||||
statement.setString(2, playerUUID.toString());
|
||||
statement.setString(3, serverUUID.toString());
|
||||
}
|
||||
});
|
||||
if (!updated) {
|
||||
execute(new ExecStatement(AllowlistBounceTable.INSERT_STATEMENT) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setString(1, playerUUID.toString());
|
||||
statement.setString(2, playerName);
|
||||
statement.setString(3, serverUUID.toString());
|
||||
statement.setInt(4, 1);
|
||||
statement.setLong(5, time);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,49 +16,33 @@
|
|||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.events;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
|
||||
import com.djrapitops.plan.delivery.webserver.http.InternalRequest;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable;
|
||||
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class StoreRequestTransaction extends Transaction {
|
||||
public class StoreRequestTransaction extends ThrowawayTransaction {
|
||||
|
||||
private final WebserverConfiguration webserverConfiguration;
|
||||
private final long timestamp;
|
||||
private final String accessAddress;
|
||||
private final String method;
|
||||
private final String url;
|
||||
private final int responseCode;
|
||||
|
||||
private final InternalRequest internalRequest;
|
||||
private final Request request; // can be null
|
||||
private final Response response;
|
||||
|
||||
public StoreRequestTransaction(WebserverConfiguration webserverConfiguration, InternalRequest internalRequest, Request request, Response response) {
|
||||
this.webserverConfiguration = webserverConfiguration;
|
||||
this.internalRequest = internalRequest;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
public StoreRequestTransaction(long timestamp, String accessAddress, String method, String url, int responseCode) {
|
||||
this.timestamp = timestamp;
|
||||
this.accessAddress = accessAddress;
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.responseCode = responseCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(new ExecStatement(AccessLogTable.INSERT_NO_USER) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, internalRequest.getTimestamp());
|
||||
statement.setString(2, internalRequest.getAccessAddress(webserverConfiguration));
|
||||
String method = internalRequest.getMethod();
|
||||
statement.setString(3, method != null ? method : "?");
|
||||
statement.setString(4, getTruncatedURI());
|
||||
statement.setInt(5, response.getCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getTruncatedURI() {
|
||||
public static String getTruncatedURI(Request request, InternalRequest internalRequest) {
|
||||
String uri = request != null ? request.getPath().asString() + request.getQuery().asString()
|
||||
: internalRequest.getRequestedURIString();
|
||||
if (uri == null) {
|
||||
|
@ -66,4 +50,18 @@ public class StoreRequestTransaction extends Transaction {
|
|||
}
|
||||
return StringUtils.truncate(uri, 65000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(new ExecStatement(AccessLogTable.INSERT_NO_USER) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, timestamp);
|
||||
statement.setString(2, StringUtils.truncate(accessAddress, 45));
|
||||
statement.setString(3, method);
|
||||
statement.setString(4, url);
|
||||
statement.setInt(5, responseCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
|
|||
executeOther(new SecurityTableIdPatch());
|
||||
execute(WebUserPreferencesTable.createTableSQL(dbType));
|
||||
execute(PluginVersionTable.createTableSQL(dbType));
|
||||
execute(AllowlistBounceTable.createTableSQL(dbType));
|
||||
|
||||
// DataExtension tables
|
||||
execute(ExtensionIconTable.createTableSQL(dbType));
|
||||
|
|
|
@ -51,6 +51,8 @@ public class CorrectWrongCharacterEncodingPatch extends Patch {
|
|||
if (dbType != DBType.MYSQL) return true;
|
||||
|
||||
correctionSqlQueries = query(getBadTableCorrectionQueries());
|
||||
// Fix for MariaDB mysql.user table being a view
|
||||
correctionSqlQueries.removeIf(sql -> sql.startsWith("ALTER TABLE `user`"));
|
||||
return correctionSqlQueries.isEmpty();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package com.djrapitops.plan.storage.database.transactions.patches;
|
||||
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable;
|
||||
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable;
|
||||
|
||||
|
@ -71,7 +71,7 @@ public class SecurityTableGroupPatch extends Patch {
|
|||
SecurityTable.USERNAME + ',' +
|
||||
SecurityTable.LINKED_TO + ',' +
|
||||
SecurityTable.SALT_PASSWORD_HASH + ',' +
|
||||
"(" + SELECT + WebGroupTable.ID + FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.NAME + "=" + (dbType == DBType.SQLITE ? "'legacy_level_' || permission_level" : "CONCAT('legacy_level_', permission_level)") + ")" +
|
||||
"(" + SELECT + WebGroupTable.ID + FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.NAME + "=" + Sql.concat(dbType, "'legacy_level_'", "permission_level") + ")" +
|
||||
FROM + tempTableName
|
||||
);
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public class UpdateWebPermissionsPatch extends Patch {
|
|||
|
||||
@Override
|
||||
public boolean hasBeenApplied() {
|
||||
List<String> defaultPermissions = Arrays.stream(WebPermission.values())
|
||||
List<String> defaultPermissions = Arrays.stream(WebPermission.nonDeprecatedValues())
|
||||
.map(WebPermission::getPermission)
|
||||
.collect(Collectors.toList());
|
||||
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
|
||||
|
@ -55,7 +55,11 @@ public class UpdateWebPermissionsPatch extends Patch {
|
|||
|
||||
@Override
|
||||
protected void applyPatch() {
|
||||
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) {
|
||||
storeMissing();
|
||||
}
|
||||
|
||||
private void storeMissing() {
|
||||
execute(new ExecBatchStatement(WebPermissionTable.safeInsertSQL(dbType)) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (String permission : missingPermissions) {
|
||||
|
|
|
@ -50,7 +50,7 @@ public class StoreMissingWebPermissionsTransaction extends Transaction {
|
|||
missingPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) {
|
||||
execute(new ExecBatchStatement(WebPermissionTable.safeInsertSQL(dbType)) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
for (String permission : missingPermissions) {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package com.djrapitops.plan.storage.file;
|
||||
|
||||
import org.apache.commons.compress.utils.IOUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
|
|
@ -18,8 +18,6 @@ package com.djrapitops.plan.storage.upkeep;
|
|||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalPlayerResultsTransaction;
|
||||
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalServerResultsTransaction;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
|
@ -112,8 +110,6 @@ public class DBCleanTask extends TaskSystem.Task {
|
|||
config.get(TimeSettings.DELETE_PING_DATA_AFTER)
|
||||
));
|
||||
database.executeTransaction(new RemoveDuplicateUserInfoTransaction());
|
||||
database.executeTransaction(new RemoveUnsatisfiedConditionalPlayerResultsTransaction());
|
||||
database.executeTransaction(new RemoveUnsatisfiedConditionalServerResultsTransaction());
|
||||
int removed = cleanOldPlayers(database);
|
||||
if (removed > 0) {
|
||||
logger.info(locale.getString(PluginLang.DB_NOTIFY_CLEAN, removed));
|
||||
|
|
|
@ -16,24 +16,59 @@
|
|||
*/
|
||||
package com.djrapitops.plan.utilities;
|
||||
|
||||
import com.djrapitops.plan.storage.database.SQLDB;
|
||||
import com.djrapitops.plan.utilities.java.ThrowableUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class SemaphoreAccessCounter {
|
||||
|
||||
private final AtomicInteger accessCounter;
|
||||
private final Object lockObject;
|
||||
private final Collection<String> holds = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public SemaphoreAccessCounter() {
|
||||
accessCounter = new AtomicInteger(0);
|
||||
lockObject = new Object();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getAccessingThing() {
|
||||
boolean previousWasAccess = false;
|
||||
List<StackTraceElement> accessors = new ArrayList<>();
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
StackTraceElement[] origin = SQLDB.getTransactionOrigin().get();
|
||||
StackTraceElement[] callSite = ThrowableUtils.combineStackTrace(origin, stackTrace);
|
||||
|
||||
for (StackTraceElement e : callSite) {
|
||||
if (previousWasAccess) {
|
||||
accessors.add(e);
|
||||
previousWasAccess = false;
|
||||
}
|
||||
String call = e.getClassName() + "." + e.getMethodName();
|
||||
if ("com.djrapitops.plan.storage.database.SQLDB.query".equals(call)
|
||||
|| "com.djrapitops.plan.storage.database.SQLDB.executeTransaction".equals(call)) {
|
||||
previousWasAccess = true;
|
||||
}
|
||||
}
|
||||
if (accessors.isEmpty()) accessors.addAll(Arrays.asList(callSite));
|
||||
return accessors.toString();
|
||||
}
|
||||
|
||||
public void enter() {
|
||||
accessCounter.incrementAndGet();
|
||||
holds.add(getAccessingThing());
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
synchronized (lockObject) {
|
||||
holds.remove(getAccessingThing());
|
||||
|
||||
int value = accessCounter.decrementAndGet();
|
||||
if (value == 0) {
|
||||
lockObject.notifyAll();
|
||||
|
@ -45,6 +80,7 @@ public class SemaphoreAccessCounter {
|
|||
while (accessCounter.get() > 0) {
|
||||
synchronized (lockObject) {
|
||||
try {
|
||||
logAccess();
|
||||
lockObject.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
@ -52,4 +88,16 @@ public class SemaphoreAccessCounter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logAccess() {
|
||||
Logger logger = Logger.getLogger("Plan");
|
||||
if (logger == null) logger = Logger.getGlobal();
|
||||
|
||||
if (logger.isLoggable(Level.INFO) && !holds.isEmpty()) {
|
||||
logger.log(Level.INFO, "Waiting for these connections to finish:");
|
||||
for (String hold : holds) {
|
||||
logger.log(Level.INFO, hold);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package com.djrapitops.plan.utilities.java;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -30,20 +32,29 @@ public class ThrowableUtils {
|
|||
/* Static method class */
|
||||
}
|
||||
|
||||
public static void appendEntryPointToCause(Throwable throwable, Throwable originPoint) {
|
||||
public static void appendEntryPointToCause(Throwable throwable, StackTraceElement[] originPoint) {
|
||||
Throwable cause = throwable.getCause();
|
||||
while (cause.getCause() != null) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
|
||||
cause.setStackTrace(
|
||||
Stream.concat(
|
||||
Arrays.stream(cause.getStackTrace()),
|
||||
Arrays.stream(originPoint.getStackTrace())
|
||||
).toArray(StackTraceElement[]::new)
|
||||
combineStackTrace(originPoint, cause.getStackTrace())
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static StackTraceElement[] combineStackTrace(StackTraceElement[] originPoint, StackTraceElement[] cause) {
|
||||
if (originPoint == null && cause == null) return new StackTraceElement[0];
|
||||
if (originPoint == null) return cause;
|
||||
if (cause == null) return originPoint;
|
||||
|
||||
return Stream.concat(
|
||||
Arrays.stream(cause),
|
||||
Arrays.stream(originPoint)
|
||||
).toArray(StackTraceElement[]::new);
|
||||
}
|
||||
|
||||
public static String findCallerAfterClass(StackTraceElement[] stackTrace, Class<?> afterThis) {
|
||||
boolean found = false;
|
||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
45VvUnNtiDHKZ+hq3vqx204q+tmLRE/koVskJLaT2+ipY8G1ThqcLZjUMuF79lYLpRIqpAt4KcY=
|
|
@ -1 +0,0 @@
|
|||
YEQ4eTdZPzUpUV4zcTp6NkE7XEw=
|
|
@ -109,10 +109,19 @@ Data_gathering:
|
|||
# Please accept the EULA to download GeoLite2 IP-Country Database
|
||||
# https://www.maxmind.com/en/geolite2/eula
|
||||
Accept_GeoLite2_EULA: false
|
||||
# This can be changed to your own MaxMind URL if you have license https://dev.maxmind.com/geoip/updating-databases?lang=en#directly-downloading-databases
|
||||
# e.g. https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY&suffix=tar.gz
|
||||
Geolocation_Download_URL: "https://geodb.playeranalytics.net/GeoLite2-Country.mmdb"
|
||||
Ping: true
|
||||
Disk_space: true
|
||||
# Does not affect already gathered data
|
||||
Preserve_join_address_case: false
|
||||
Join_addresses:
|
||||
Enabled: true
|
||||
# Does not affect already gathered data
|
||||
Preserve_case: false
|
||||
Preserve_invalid: false
|
||||
# Replaces these join addresses with unknown
|
||||
Filter_out_from_data:
|
||||
- "play.example.com"
|
||||
# -----------------------------------------------------
|
||||
# Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
|
||||
# -----------------------------------------------------
|
||||
|
|
|
@ -110,13 +110,22 @@ Data_gathering:
|
|||
# Please accept the EULA to download GeoLite2 IP-Country Database
|
||||
# https://www.maxmind.com/en/geolite2/eula
|
||||
Accept_GeoLite2_EULA: false
|
||||
# This can be changed to your own MaxMind URL if you have license https://dev.maxmind.com/geoip/updating-databases?lang=en#directly-downloading-databases
|
||||
# e.g. https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY&suffix=tar.gz
|
||||
Geolocation_Download_URL: "https://geodb.playeranalytics.net/GeoLite2-Country.mmdb"
|
||||
Ping: true
|
||||
Disk_space: true
|
||||
Commands:
|
||||
Log_unknown: false
|
||||
Log_aliases_as_main_command: true
|
||||
# Does not affect already gathered data
|
||||
Preserve_join_address_case: false
|
||||
Join_addresses:
|
||||
Enabled: true
|
||||
# Does not affect already gathered data
|
||||
Preserve_case: false
|
||||
Preserve_invalid: false
|
||||
# Replaces these join addresses with unknown
|
||||
Filter_out_from_data:
|
||||
- "play.example.com"
|
||||
# -----------------------------------------------------
|
||||
# Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
|
||||
# -----------------------------------------------------
|
||||
|
|
|
@ -184,7 +184,7 @@ command:
|
|||
inDepth: "获得一个指向 /players page(全体玩家页面) 的链接,以查看玩家列表。"
|
||||
register:
|
||||
description: "注册一个网页用户"
|
||||
inDepth: "直接使用会获得注册页面的链接。添加 --code[注册代码] 参数可以注册一个账户。"
|
||||
inDepth: "直接使用会获得注册页面的链接。添加 --code [注册代码] 参数可以注册一个账户。"
|
||||
reload:
|
||||
description: "重启 Plan"
|
||||
inDepth: "禁用然后重新启用本插件,会重新加载配置中的设置。"
|
||||
|
@ -290,13 +290,19 @@ html:
|
|||
active: "活跃"
|
||||
activePlaytime: "活跃时间"
|
||||
activityIndex: "活跃指数"
|
||||
addJoinAddressGroup: "添加地址组"
|
||||
addressGroup: "地址组 {{n}}"
|
||||
afk: "挂机"
|
||||
afkTime: "挂机时间"
|
||||
all: "全部"
|
||||
allTime: "所有时间"
|
||||
allowed: "允许"
|
||||
allowlist: "白名单"
|
||||
allowlistBounces: "白名单退回"
|
||||
alphabetical: "按字母顺序"
|
||||
apply: "应用"
|
||||
asNumbers: "数据"
|
||||
attempts: "尝试"
|
||||
average: "平均"
|
||||
averageActivePlaytime: "平均活跃时间"
|
||||
averageAfkTime: "平均挂机时间"
|
||||
|
@ -317,6 +323,7 @@ html:
|
|||
banned: "已被封禁"
|
||||
bestPeak: "历史最高峰值"
|
||||
bestPing: "最低延迟"
|
||||
blocked: "阻止"
|
||||
calendar: " 日历"
|
||||
comparing7days: "对比 7 天的情况"
|
||||
connectionInfo: "连接信息"
|
||||
|
@ -338,6 +345,7 @@ html:
|
|||
duringLowTps: "持续低 TPS 时间"
|
||||
entities: "实体"
|
||||
errors: "Plan 错误日志"
|
||||
export: "导出"
|
||||
exported: "数据导出时间"
|
||||
favoriteServer: "最喜欢的服务器"
|
||||
firstSession: "第一次会话"
|
||||
|
@ -429,7 +437,10 @@ html:
|
|||
last24hours: "过去 24 小时"
|
||||
last30days: "过去 30 天"
|
||||
last7days: "过去 7 天"
|
||||
lastAllowed: "最后允许"
|
||||
lastBlocked: "最后阻止"
|
||||
lastConnected: "最后连接时间"
|
||||
lastKnownAttempt: "最后一次已知的尝试"
|
||||
lastPeak: "上次在线峰值"
|
||||
lastSeen: "最后在线时间"
|
||||
latestJoinAddresses: "上一次加入地址"
|
||||
|
@ -655,7 +666,6 @@ html:
|
|||
page_network_geolocations_ping_per_country: "查看按国家划分的Ping表"
|
||||
page_network_join_addresses: "查看加入地址 - 选项卡"
|
||||
page_network_join_addresses_graphs: "查看加入地址图表"
|
||||
page_network_join_addresses_graphs_pie: "查看最新加入地址图表"
|
||||
page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表"
|
||||
page_network_overview: "查看网络总览 - 选项卡"
|
||||
page_network_overview_graphs: "查看网络总览图表"
|
||||
|
@ -685,12 +695,12 @@ html:
|
|||
page_player_sessions: "查看玩家会话 - 选项卡"
|
||||
page_player_versus: "查看PvP和PvE - 选项卡"
|
||||
page_server: "查看所有服务器页面"
|
||||
page_server_allowlist_bounce: "查看游戏白名单退回列表"
|
||||
page_server_geolocations: "查看服务器地理位置 - 选项卡"
|
||||
page_server_geolocations_map: "查看服务器地理位置地图"
|
||||
page_server_geolocations_ping_per_country: "查看按国家划分的延迟表"
|
||||
page_server_join_addresses: "查看服务器加入地址 - 选项卡"
|
||||
page_server_join_addresses_graphs: "查看服务器加入地址图表"
|
||||
page_server_join_addresses_graphs_pie: "查看最新加入地址图表"
|
||||
page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表"
|
||||
page_server_online_activity: "查看在线活动 - 选项卡"
|
||||
page_server_online_activity_graphs: "查看在线活动图表"
|
||||
|
@ -784,6 +794,7 @@ html:
|
|||
generic:
|
||||
are: "`是`"
|
||||
label:
|
||||
editQuery: "修改查询"
|
||||
from: ">从 </label>"
|
||||
makeAnother: "进行另一个查询"
|
||||
servers:
|
||||
|
@ -896,7 +907,7 @@ plugin:
|
|||
no: "否"
|
||||
today: "'今天'"
|
||||
unavailable: "不可用"
|
||||
unknown: "位置"
|
||||
unknown: "未知"
|
||||
yes: "是"
|
||||
yesterday: "'昨天'"
|
||||
localeReloaded: "自定义 locale.yml 已被修改,因此已重新加载并且现在正在使用。"
|
||||
|
|
|
@ -290,13 +290,19 @@ html:
|
|||
active: "Aktivní"
|
||||
activePlaytime: "Aktivní herní čas"
|
||||
activityIndex: "Index aktivity"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK čas"
|
||||
all: "Vše"
|
||||
allTime: "Celkově"
|
||||
allowed: "Allowed"
|
||||
allowlist: "Allowlist"
|
||||
allowlistBounces: "Allowlist Bounces"
|
||||
alphabetical: "Abecední řazení"
|
||||
apply: "Apply"
|
||||
asNumbers: "statistiky"
|
||||
attempts: "Attempts"
|
||||
average: "Průměrná délka prvního připojení"
|
||||
averageActivePlaytime: "Průměrná herní aktivita"
|
||||
averageAfkTime: "Průměrný AFK čas"
|
||||
|
@ -317,6 +323,7 @@ html:
|
|||
banned: "Zabanován"
|
||||
bestPeak: "Nejvíce hráčů"
|
||||
bestPing: "Nejlepší ping"
|
||||
blocked: "Blocked"
|
||||
calendar: " Kalendář"
|
||||
comparing7days: "Srovnání posledních 7 dní"
|
||||
connectionInfo: "Informace o připojení"
|
||||
|
@ -338,6 +345,7 @@ html:
|
|||
duringLowTps: "Při nízkých TPS:"
|
||||
entities: "Entity"
|
||||
errors: "Plan Error Logs"
|
||||
export: "Export"
|
||||
exported: "Doba exportu dat"
|
||||
favoriteServer: "Oblíbený server"
|
||||
firstSession: "První relace"
|
||||
|
@ -429,7 +437,10 @@ html:
|
|||
last24hours: "Posledních 24 hodin"
|
||||
last30days: "Posledních 30 dní"
|
||||
last7days: "Posledních 7 dní"
|
||||
lastAllowed: "Last Allowed"
|
||||
lastBlocked: "Last Blocked"
|
||||
lastConnected: "Poslední připojení"
|
||||
lastKnownAttempt: "Last Known Attempt"
|
||||
lastPeak: "Naposledy nejvíce hráčů"
|
||||
lastSeen: "Naposledy viděn"
|
||||
latestJoinAddresses: "Poslední adresy pro připojení"
|
||||
|
@ -655,7 +666,6 @@ html:
|
|||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_network_overview: "See Network Overview -tab"
|
||||
page_network_overview_graphs: "See Network Overview graphs"
|
||||
|
@ -685,12 +695,12 @@ html:
|
|||
page_player_sessions: "See Player Sessions -tab"
|
||||
page_player_versus: "See PvP & PvE -tab"
|
||||
page_server: "See all of server page"
|
||||
page_server_allowlist_bounce: "See list of Game allowlist bounces"
|
||||
page_server_geolocations: "See Geolocations tab"
|
||||
page_server_geolocations_map: "See Geolocations Map"
|
||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_server_join_addresses: "See Join Addresses -tab"
|
||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_server_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -784,6 +794,7 @@ html:
|
|||
generic:
|
||||
are: "` jsou`"
|
||||
label:
|
||||
editQuery: "Edit Query"
|
||||
from: ">od</label>"
|
||||
makeAnother: "Vytvořit další dotaz"
|
||||
servers:
|
||||
|
|
|
@ -290,13 +290,19 @@ html:
|
|||
active: "Aktiv"
|
||||
activePlaytime: "Aktive Spielzeit"
|
||||
activityIndex: "Aktivitätsindex"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Zeit"
|
||||
all: "Gesamt"
|
||||
allTime: "Gesamte zeit"
|
||||
allowed: "Allowed"
|
||||
allowlist: "Allowlist"
|
||||
allowlistBounces: "Allowlist Bounces"
|
||||
alphabetical: "Alphabetical"
|
||||
apply: "Apply"
|
||||
asNumbers: "als Zahlen"
|
||||
attempts: "Attempts"
|
||||
average: "Average first session length"
|
||||
averageActivePlaytime: "Durchschnittliche aktive Spielzeit"
|
||||
averageAfkTime: "Durchschnittliche AFK Zeit"
|
||||
|
@ -317,6 +323,7 @@ html:
|
|||
banned: "Gebannt"
|
||||
bestPeak: "Rekord"
|
||||
bestPing: "Bester Ping"
|
||||
blocked: "Blocked"
|
||||
calendar: " Kalender"
|
||||
comparing7days: "Vergleiche 7 Tage"
|
||||
connectionInfo: "Verbindungsinformationen"
|
||||
|
@ -338,6 +345,7 @@ html:
|
|||
duringLowTps: "Während niedriger TPS-Spitzen:"
|
||||
entities: "Entitäten"
|
||||
errors: "Plan Error Logs"
|
||||
export: "Export"
|
||||
exported: "Data export time"
|
||||
favoriteServer: "Lieblingsserver"
|
||||
firstSession: "Erste Sitzung"
|
||||
|
@ -429,7 +437,10 @@ html:
|
|||
last24hours: "Letzte 24 Stunden"
|
||||
last30days: "Letzte 30 Tage"
|
||||
last7days: "Letzte 7 Tage"
|
||||
lastAllowed: "Last Allowed"
|
||||
lastBlocked: "Last Blocked"
|
||||
lastConnected: "Letzte Verbindung"
|
||||
lastKnownAttempt: "Last Known Attempt"
|
||||
lastPeak: "Letzter Höchststand"
|
||||
lastSeen: "Zuletzt gesehen"
|
||||
latestJoinAddresses: "Latest Join Addresses"
|
||||
|
@ -655,7 +666,6 @@ html:
|
|||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_network_overview: "See Network Overview -tab"
|
||||
page_network_overview_graphs: "See Network Overview graphs"
|
||||
|
@ -685,12 +695,12 @@ html:
|
|||
page_player_sessions: "See Player Sessions -tab"
|
||||
page_player_versus: "See PvP & PvE -tab"
|
||||
page_server: "See all of server page"
|
||||
page_server_allowlist_bounce: "See list of Game allowlist bounces"
|
||||
page_server_geolocations: "See Geolocations tab"
|
||||
page_server_geolocations_map: "See Geolocations Map"
|
||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_server_join_addresses: "See Join Addresses -tab"
|
||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_server_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -784,6 +794,7 @@ html:
|
|||
generic:
|
||||
are: "`are`"
|
||||
label:
|
||||
editQuery: "Edit Query"
|
||||
from: ">from</label>"
|
||||
makeAnother: "Make another query"
|
||||
servers:
|
||||
|
|
|
@ -290,13 +290,19 @@ html:
|
|||
active: "Active"
|
||||
activePlaytime: "Active Playtime"
|
||||
activityIndex: "Activity Index"
|
||||
addJoinAddressGroup: "Add address group"
|
||||
addressGroup: "Address group {{n}}"
|
||||
afk: "AFK"
|
||||
afkTime: "AFK Time"
|
||||
all: "All"
|
||||
allTime: "All Time"
|
||||
allowed: "Allowed"
|
||||
allowlist: "Allowlist"
|
||||
allowlistBounces: "Allowlist Bounces"
|
||||
alphabetical: "Alphabetical"
|
||||
apply: "Apply"
|
||||
asNumbers: "as Numbers"
|
||||
attempts: "Attempts"
|
||||
average: "Average first session length"
|
||||
averageActivePlaytime: "Average Active Playtime"
|
||||
averageAfkTime: "Average AFK Time"
|
||||
|
@ -317,6 +323,7 @@ html:
|
|||
banned: "Banned"
|
||||
bestPeak: "All Time Peak"
|
||||
bestPing: "Best Ping"
|
||||
blocked: "Blocked"
|
||||
calendar: " Calendar"
|
||||
comparing7days: "Comparing 7 days"
|
||||
connectionInfo: "Connection Information"
|
||||
|
@ -338,6 +345,7 @@ html:
|
|||
duringLowTps: "During Low TPS Spikes:"
|
||||
entities: "Entities"
|
||||
errors: "Plan Error Logs"
|
||||
export: "Export"
|
||||
exported: "Data export time"
|
||||
favoriteServer: "Favorite Server"
|
||||
firstSession: "First session"
|
||||
|
@ -429,7 +437,10 @@ html:
|
|||
last24hours: "Last 24 hours"
|
||||
last30days: "Last 30 days"
|
||||
last7days: "Last 7 days"
|
||||
lastAllowed: "Last Allowed"
|
||||
lastBlocked: "Last Blocked"
|
||||
lastConnected: "Last Connected"
|
||||
lastKnownAttempt: "Last Known Attempt"
|
||||
lastPeak: "Last Peak"
|
||||
lastSeen: "Last Seen"
|
||||
latestJoinAddresses: "Latest Join Addresses"
|
||||
|
@ -655,7 +666,6 @@ html:
|
|||
page_network_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_network_join_addresses: "See Join Addresses -tab"
|
||||
page_network_join_addresses_graphs: "See Join Address graphs"
|
||||
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_network_overview: "See Network Overview -tab"
|
||||
page_network_overview_graphs: "See Network Overview graphs"
|
||||
|
@ -685,12 +695,12 @@ html:
|
|||
page_player_sessions: "See Player Sessions -tab"
|
||||
page_player_versus: "See PvP & PvE -tab"
|
||||
page_server: "See all of server page"
|
||||
page_server_allowlist_bounce: "See list of Game allowlist bounces"
|
||||
page_server_geolocations: "See Geolocations tab"
|
||||
page_server_geolocations_map: "See Geolocations Map"
|
||||
page_server_geolocations_ping_per_country: "See Ping Per Country table"
|
||||
page_server_join_addresses: "See Join Addresses -tab"
|
||||
page_server_join_addresses_graphs: "See Join Address graphs"
|
||||
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
|
||||
page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
|
||||
page_server_online_activity: "See Online Activity -tab"
|
||||
page_server_online_activity_graphs: "See Online Activity graphs"
|
||||
|
@ -784,6 +794,7 @@ html:
|
|||
generic:
|
||||
are: "`are`"
|
||||
label:
|
||||
editQuery: "Edit Query"
|
||||
from: ">from</label>"
|
||||
makeAnother: "Make another query"
|
||||
servers:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue